vitae 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -2
- data/bin/vitae +1 -63
- data/lib/vitae.rb +25 -1
- data/lib/vitae/cli.rb +69 -0
- data/lib/vitae/cv.rb +55 -16
- data/lib/vitae/ordered_hash.rb +188 -0
- data/lib/vitae/server/assets/favicon.ico +0 -0
- data/lib/vitae/server/helpers.rb +3 -2
- data/lib/vitae/server/server.rb +5 -12
- data/lib/vitae/server/views/index.haml +3 -1
- data/lib/vitae/server/views/layout.haml +1 -3
- data/lib/vitae/server/views/show.haml +20 -0
- data/lib/vitae/server/views/vcard.haml +39 -0
- data/lib/vitae/templates/cvs/arthur_gunn.yaml +21 -4
- data/lib/vitae/templates/cvs/default.yaml.tt +10 -4
- data/lib/vitae/templates/themes/default/application.css +14 -0
- data/lib/vitae/version.rb +1 -1
- data/test/active_server_test.rb +23 -16
- data/test/cv_format_test.rb +28 -0
- data/test/test_helper.rb +86 -23
- data/test/utilities_test.rb +51 -0
- data/test/vitae_executable_test.rb +16 -10
- data/vitae.gemspec +1 -1
- metadata +13 -5
data/README.rdoc
CHANGED
@@ -22,12 +22,12 @@ Great, it's created our two named CVs in the my_cv_project/cvs directory from a
|
|
22
22
|
Once your done editing, start up the server:
|
23
23
|
$: cd my_cv_project
|
24
24
|
$: vitae server
|
25
|
-
Serving CVs at 0.0.0.0:3141
|
25
|
+
Serving 2 CVs at http://0.0.0.0:3141/ from my_cv_project
|
26
26
|
|
27
27
|
Enjoy!
|
28
28
|
|
29
29
|
=== Future plans
|
30
|
-
1.
|
30
|
+
1. All data read from the yaml files should be displayed sensibly.
|
31
31
|
2. Vitae should com bundled with better default templates - CSS, JS and YAML.
|
32
32
|
3. Support for multiple themes.
|
33
33
|
4. The clever stuff
|
data/bin/vitae
CHANGED
@@ -1,67 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require "rubygems"
|
3
|
-
require "thor"
|
4
|
-
|
5
2
|
require File.expand_path('../../lib/vitae', __FILE__)
|
6
|
-
|
7
|
-
module Vitae
|
8
|
-
class CLI < Thor
|
9
|
-
include Thor::Actions
|
10
|
-
|
11
|
-
def self.source_root
|
12
|
-
File.expand_path('../../lib/vitae/templates', __FILE__)
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
default_task :create
|
17
|
-
|
18
|
-
desc "create [project_name]", "Create a new vitae project"
|
19
|
-
def create(name, *cv_names)
|
20
|
-
self.destination_root = name
|
21
|
-
puts "creating #{name}..."
|
22
|
-
|
23
|
-
if cv_names.size > 0
|
24
|
-
cv_names.each do |cv_name|
|
25
|
-
@name = cv_name
|
26
|
-
template "cvs/default.yaml.tt", "cvs/#{cv_name}.yaml"
|
27
|
-
end
|
28
|
-
else
|
29
|
-
templates = Dir.glob(File.join(CLI.source_root, "cvs/*.yaml*")).
|
30
|
-
map{|f| File.basename(f)} -["default.yaml.tt"]
|
31
|
-
|
32
|
-
templates.each do |template_name|
|
33
|
-
template "cvs/#{template_name}", "cvs/#{template_name.sub(/\.tt$/, '')}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
themes = Dir.glob(File.join(CLI.source_root, "themes/*")).map{|f| File.basename(f)}
|
38
|
-
themes.each do |theme_name|
|
39
|
-
copy_file "themes/#{theme_name}/application.js"
|
40
|
-
copy_file "themes/#{theme_name}/application.css"
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
desc "server [-p 3000]", "Start the vitae server."
|
46
|
-
def server
|
47
|
-
require "rack"
|
48
|
-
|
49
|
-
just_pretend = ARGV.delete "--pretend"
|
50
|
-
port_set_by_user = ARGV.any? { |a| %w[-p --port].include?( a ) }
|
51
|
-
|
52
|
-
server = Rack::Server.new.tap do |s|
|
53
|
-
s.options[:config] = File.join(File.dirname(__FILE__), "../config.ru")
|
54
|
-
s.options[:Port] = 3141 unless port_set_by_user
|
55
|
-
end
|
56
|
-
|
57
|
-
require "vitae/server/server"
|
58
|
-
Server.project_root = Dir.pwd
|
59
|
-
|
60
|
-
say "Serving CVs at #{server.options[:Host]}:#{server.options[:Port]}", :green
|
61
|
-
server.start unless just_pretend
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|
65
|
-
end
|
3
|
+
require "vitae/cli"
|
66
4
|
|
67
5
|
Vitae::CLI.start
|
data/lib/vitae.rb
CHANGED
@@ -1,2 +1,26 @@
|
|
1
1
|
$:.unshift File.dirname(File.expand_path(__FILE__))
|
2
|
-
require "vitae/cv"
|
2
|
+
require "vitae/cv"
|
3
|
+
require "vitae/ordered_hash"
|
4
|
+
|
5
|
+
module Vitae
|
6
|
+
@@project_root = nil
|
7
|
+
def self.project_root
|
8
|
+
@@project_root
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.project_root= root
|
12
|
+
Server.set :public, File.join(root, "themes") if root && defined?(Server)
|
13
|
+
@@project_root = root
|
14
|
+
end
|
15
|
+
|
16
|
+
module OrderedHashExtensions
|
17
|
+
def except(exceptions=[])
|
18
|
+
reject do |k, v|
|
19
|
+
exceptions.include? k
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
Hash.send :include, Vitae::OrderedHashExtensions
|
data/lib/vitae/cli.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "thor"
|
3
|
+
|
4
|
+
module Vitae
|
5
|
+
class CLI < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
File.expand_path('templates', File.dirname(__FILE__))
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
default_task :create
|
14
|
+
|
15
|
+
desc "create [project_name]", "Create a new vitae project"
|
16
|
+
def create(name, *cv_names)
|
17
|
+
self.destination_root = name
|
18
|
+
puts "creating #{name}..."
|
19
|
+
|
20
|
+
if cv_names.size > 0
|
21
|
+
cv_names.each do |cv_name|
|
22
|
+
@name = cv_name
|
23
|
+
@human_name = cv_name.split(/[^a-z]+/i).map{|w|w.capitalize}.join(" ")
|
24
|
+
@url_name = @name.gsub(/[^a-z\-]+/i, "-")
|
25
|
+
template "cvs/default.yaml.tt", "cvs/#{cv_name}.yaml"
|
26
|
+
end
|
27
|
+
else
|
28
|
+
templates = Dir.glob(File.join(CLI.source_root, "cvs/*.yaml*")).
|
29
|
+
map{|f| File.basename(f)} -["default.yaml.tt"]
|
30
|
+
|
31
|
+
templates.each do |template_name|
|
32
|
+
template "cvs/#{template_name}", "cvs/#{template_name.sub(/\.tt$/, '')}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
themes = Dir.glob(File.join(CLI.source_root, "themes/*")).map{|f| File.basename(f)}
|
37
|
+
themes.each do |theme_name|
|
38
|
+
copy_file "themes/#{theme_name}/application.js"
|
39
|
+
copy_file "themes/#{theme_name}/application.css"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "server [-p 3000]", "Start the vitae server."
|
45
|
+
def server(project_path=".")
|
46
|
+
require "rack"
|
47
|
+
just_pretend = ARGV.delete "--pretend"
|
48
|
+
|
49
|
+
port_set_by_user = ARGV.any? { |a| %w[-p --port].include?( a ) }
|
50
|
+
|
51
|
+
server = Rack::Server.new.tap do |s|
|
52
|
+
s.options[:config] = File.join(File.dirname(__FILE__), "..", "..", "config.ru")
|
53
|
+
s.options[:Port] = 3141 unless port_set_by_user
|
54
|
+
end
|
55
|
+
|
56
|
+
require "vitae/server/server"
|
57
|
+
Vitae::project_root = File.expand_path(project_path, Dir.pwd)
|
58
|
+
|
59
|
+
opts = server.options
|
60
|
+
cv_count = CV.all.size
|
61
|
+
cvs = "#{cv_count} CV#{'s' unless cv_count==1}"
|
62
|
+
project_dir_name = File.basename(Vitae::project_root)
|
63
|
+
|
64
|
+
say "Serving #{cvs} at http://#{opts[:Host]}:#{opts[:Port]}/ from #{project_dir_name}\n", :green
|
65
|
+
server.start unless just_pretend
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/vitae/cv.rb
CHANGED
@@ -1,40 +1,79 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
1
3
|
class CV
|
4
|
+
attr_reader :file_name
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
class << self
|
7
|
+
def all
|
8
|
+
raise "Vitae::project_root is not set" unless Vitae::project_root
|
9
|
+
cvs = Dir.glob(File.join(Vitae::project_root, "cvs/*.yaml"))
|
10
|
+
cvs.map do |cv|
|
11
|
+
CV.new(cv)
|
12
|
+
end
|
7
13
|
end
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
|
15
|
+
def find file_name
|
16
|
+
all.find do |cv|
|
17
|
+
cv.file_name == file_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def first
|
22
|
+
all.first
|
23
|
+
end
|
24
|
+
|
25
|
+
def last
|
26
|
+
all.last
|
13
27
|
end
|
14
28
|
end
|
15
29
|
|
16
30
|
def initialize yaml_file
|
17
|
-
@
|
31
|
+
@yaml_file = yaml_file
|
32
|
+
@file_name = File.basename(yaml_file, '.yaml')
|
18
33
|
end
|
19
34
|
|
35
|
+
|
20
36
|
def name
|
21
|
-
@name
|
37
|
+
@name ||= data_hash.delete("name")
|
22
38
|
end
|
23
39
|
|
24
|
-
def
|
25
|
-
|
40
|
+
def vitae_config
|
41
|
+
@vitae_config ||= data_hash.delete("vitae_config")
|
26
42
|
end
|
27
43
|
|
28
|
-
def
|
29
|
-
|
44
|
+
def data_hash
|
45
|
+
@data_hash ||= YAML::load_file(@yaml_file)
|
46
|
+
end
|
47
|
+
|
48
|
+
def [] key
|
49
|
+
data_hash[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
def except exceptions=[]
|
53
|
+
data_hash.except( exceptions )
|
30
54
|
end
|
31
55
|
|
56
|
+
def each &block
|
57
|
+
data_hash.each do |k, v|
|
58
|
+
yield k, v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
32
63
|
def link
|
33
|
-
"/#{
|
64
|
+
"/#{file_name}"
|
34
65
|
end
|
35
66
|
|
36
67
|
def theme
|
37
68
|
"default"
|
38
69
|
end
|
39
70
|
|
71
|
+
def to_s
|
72
|
+
name
|
73
|
+
end
|
74
|
+
|
75
|
+
# def method_missing name, *args, &block
|
76
|
+
# data.send name, *args, &block
|
77
|
+
# end
|
78
|
+
|
40
79
|
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
YAML.add_builtin_type("omap") do |type, val|
|
4
|
+
ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)]
|
5
|
+
end
|
6
|
+
|
7
|
+
module ActiveSupport
|
8
|
+
# 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
|
+
# implements a hash that preserves insertion order, as in Ruby 1.9:
|
11
|
+
#
|
12
|
+
# oh = ActiveSupport::OrderedHash.new
|
13
|
+
# oh[:a] = 1
|
14
|
+
# oh[:b] = 2
|
15
|
+
# oh.keys # => [:a, :b], this order is guaranteed
|
16
|
+
#
|
17
|
+
# <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
|
18
|
+
class OrderedHash < ::Hash #:nodoc:
|
19
|
+
def to_yaml_type
|
20
|
+
"!tag:yaml.org,2002:omap"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_yaml(opts = {})
|
24
|
+
YAML.quick_emit(self, opts) do |out|
|
25
|
+
out.seq(taguri, to_yaml_style) do |seq|
|
26
|
+
each do |k, v|
|
27
|
+
seq.add(k => v)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Hash is ordered in Ruby 1.9!
|
34
|
+
if RUBY_VERSION < '1.9'
|
35
|
+
|
36
|
+
# In MRI the Hash class is core and written in C. In particular, methods are
|
37
|
+
# programmed with explicit C function calls and polymorphism is not honored.
|
38
|
+
#
|
39
|
+
# For example, []= is crucial in this implementation to maintain the @keys
|
40
|
+
# array but hash.c invokes rb_hash_aset() originally. This prevents method
|
41
|
+
# reuse through inheritance and forces us to reimplement stuff.
|
42
|
+
#
|
43
|
+
# For instance, we cannot use the inherited #merge! because albeit the algorithm
|
44
|
+
# itself would work, our []= is not being called at all by the C code.
|
45
|
+
|
46
|
+
def initialize(*args, &block)
|
47
|
+
super
|
48
|
+
@keys = []
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.[](*args)
|
52
|
+
ordered_hash = new
|
53
|
+
|
54
|
+
if (args.length == 1 && args.first.is_a?(Array))
|
55
|
+
args.first.each do |key_value_pair|
|
56
|
+
next unless (key_value_pair.is_a?(Array))
|
57
|
+
ordered_hash[key_value_pair[0]] = key_value_pair[1]
|
58
|
+
end
|
59
|
+
|
60
|
+
return ordered_hash
|
61
|
+
end
|
62
|
+
|
63
|
+
unless (args.size % 2 == 0)
|
64
|
+
raise ArgumentError.new("odd number of arguments for Hash")
|
65
|
+
end
|
66
|
+
|
67
|
+
args.each_with_index do |val, ind|
|
68
|
+
next if (ind % 2 != 0)
|
69
|
+
ordered_hash[val] = args[ind + 1]
|
70
|
+
end
|
71
|
+
|
72
|
+
ordered_hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize_copy(other)
|
76
|
+
super
|
77
|
+
# make a deep copy of keys
|
78
|
+
@keys = other.keys
|
79
|
+
end
|
80
|
+
|
81
|
+
def []=(key, value)
|
82
|
+
@keys << key unless has_key?(key)
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete(key)
|
87
|
+
if has_key? key
|
88
|
+
index = @keys.index(key)
|
89
|
+
@keys.delete_at index
|
90
|
+
end
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
94
|
+
def delete_if
|
95
|
+
super
|
96
|
+
sync_keys!
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def reject!
|
101
|
+
super
|
102
|
+
sync_keys!
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def reject(&block)
|
107
|
+
dup.reject!(&block)
|
108
|
+
end
|
109
|
+
|
110
|
+
def keys
|
111
|
+
@keys.dup
|
112
|
+
end
|
113
|
+
|
114
|
+
def values
|
115
|
+
@keys.collect { |key| self[key] }
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_hash
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_a
|
123
|
+
@keys.map { |key| [ key, self[key] ] }
|
124
|
+
end
|
125
|
+
|
126
|
+
def each_key
|
127
|
+
@keys.each { |key| yield key }
|
128
|
+
end
|
129
|
+
|
130
|
+
def each_value
|
131
|
+
@keys.each { |key| yield self[key]}
|
132
|
+
end
|
133
|
+
|
134
|
+
def each
|
135
|
+
@keys.each {|key| yield [key, self[key]]}
|
136
|
+
end
|
137
|
+
|
138
|
+
alias_method :each_pair, :each
|
139
|
+
|
140
|
+
def clear
|
141
|
+
super
|
142
|
+
@keys.clear
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
def shift
|
147
|
+
k = @keys.first
|
148
|
+
v = delete(k)
|
149
|
+
[k, v]
|
150
|
+
end
|
151
|
+
|
152
|
+
def merge!(other_hash)
|
153
|
+
if block_given?
|
154
|
+
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
|
155
|
+
else
|
156
|
+
other_hash.each { |k, v| self[k] = v }
|
157
|
+
end
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
alias_method :update, :merge!
|
162
|
+
|
163
|
+
def merge(other_hash, &block)
|
164
|
+
dup.merge!(other_hash, &block)
|
165
|
+
end
|
166
|
+
|
167
|
+
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
|
168
|
+
def replace(other)
|
169
|
+
super
|
170
|
+
@keys = other.keys
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
def invert
|
175
|
+
OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
|
176
|
+
end
|
177
|
+
|
178
|
+
def inspect
|
179
|
+
"#<OrderedHash #{super}>"
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def sync_keys!
|
184
|
+
@keys.delete_if {|k| !has_key?(k)}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
Binary file
|
data/lib/vitae/server/helpers.rb
CHANGED
@@ -9,8 +9,9 @@ module Helpers
|
|
9
9
|
html
|
10
10
|
end
|
11
11
|
|
12
|
-
def link_to(text,
|
13
|
-
|
12
|
+
def link_to(text, *args)
|
13
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
14
|
+
link = args.first || text.split(/\s+/).join('_').downcase
|
14
15
|
content_tag :a, text, options.merge({:href => link})
|
15
16
|
end
|
16
17
|
|
data/lib/vitae/server/server.rb
CHANGED
@@ -5,27 +5,20 @@ require "vitae/server/helpers"
|
|
5
5
|
class Server < Sinatra::Base
|
6
6
|
helpers Helpers
|
7
7
|
set :views, File.dirname(__FILE__) + '/views'
|
8
|
+
set :public, File.join(Vitae::project_root, "themes") rescue ''
|
8
9
|
|
9
10
|
get '/' do
|
10
11
|
@cvs = CV.all
|
11
12
|
haml :index
|
12
13
|
end
|
13
14
|
|
15
|
+
get "/favicon.ico" do
|
16
|
+
send_file File.join(File.dirname(__FILE__), "assets", "favicon.ico"), :type => 'image/x-icon', :disposition => 'inline'
|
17
|
+
end
|
18
|
+
|
14
19
|
get '/:name' do
|
15
20
|
@cv = CV.find( params[:name] )
|
16
21
|
haml :show
|
17
22
|
end
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
@@project_root = nil
|
22
|
-
def self.project_root
|
23
|
-
@@project_root
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.project_root= root
|
27
|
-
set :public, File.join(root, "themes")
|
28
|
-
@@project_root = root
|
29
|
-
end
|
30
|
-
|
31
24
|
end
|
@@ -1 +1,21 @@
|
|
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
|
19
|
+
%br
|
20
|
+
%br
|
1
21
|
=link_to "Vitae Home", "/"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
.vcard
|
2
|
+
%h1#title.fn= @cv.name
|
3
|
+
|
4
|
+
%ul#quick-links
|
5
|
+
- @cv["contact"].each do |key, value|
|
6
|
+
- case value
|
7
|
+
- when /^https?:\/\//
|
8
|
+
%li
|
9
|
+
%span.label= key
|
10
|
+
= link_to value.sub(/^https?:\/\//, ''), value, :class => "url"
|
11
|
+
- when Hash
|
12
|
+
- case key
|
13
|
+
- when "phones"
|
14
|
+
- value.each do |phone, number|
|
15
|
+
%li.tel
|
16
|
+
%span.label.type= phone
|
17
|
+
%span.value= number
|
18
|
+
- when "address"
|
19
|
+
%li
|
20
|
+
%span.label= key
|
21
|
+
%span.adr
|
22
|
+
- value.each_with_index do |(address_field, address_value), i|
|
23
|
+
= succeed "#{', ' if i<value.size-1}" do
|
24
|
+
%span{:class => address_field}= address_value
|
25
|
+
- else
|
26
|
+
- case key
|
27
|
+
- when "nickname"
|
28
|
+
%li.nick
|
29
|
+
%span.label= key
|
30
|
+
%span.fn.nickname= value
|
31
|
+
- when /e-?mail/
|
32
|
+
%li
|
33
|
+
%span.label= key
|
34
|
+
- mailto_less = value.sub(/^mailto:/, '')
|
35
|
+
= link_to mailto_less, "mailto:#{mailto_less}", :class => "email"
|
36
|
+
- else
|
37
|
+
%li
|
38
|
+
%span.label= key
|
39
|
+
= value
|
@@ -1,5 +1,22 @@
|
|
1
|
-
|
1
|
+
--- !omap
|
2
|
+
- name: Arthur Gunn
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
- vitae_config: !omap
|
5
|
+
- email: arthur@gunn.co.nz
|
6
|
+
- crypted_password: d7f42977c55e23fc92e4190c6adc07862a572879
|
7
|
+
- password_salt: 8f7a8cf04f4e
|
8
|
+
|
9
|
+
- contact: !omap
|
10
|
+
- nickname: Arthur
|
11
|
+
- e-mail: mailto:arthur@gunn.co.nz
|
12
|
+
- homepage: http://arthurgunn.com
|
13
|
+
- github: https://github.com/gunn
|
14
|
+
- address: !omap
|
15
|
+
- street-address: 96 London St.
|
16
|
+
- locality: City Rise
|
17
|
+
- region: Otago
|
18
|
+
- country-name: New Zealand
|
19
|
+
- postal-code: 9016
|
20
|
+
- phones: !omap
|
21
|
+
- home: (03) 477 99 44
|
22
|
+
- mobile: 027 477 7723
|
@@ -1,5 +1,11 @@
|
|
1
|
-
|
1
|
+
--- !omap
|
2
|
+
- name: <%= @human_name %>
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
- vitae_config: !omap
|
5
|
+
- email: <%= @url_name %>@gmail.com
|
6
|
+
- crypted_password: d7f42977c55e23fc92e4190c6adc07862a572879
|
7
|
+
- password_salt: 8f7a8cf04f4e
|
8
|
+
|
9
|
+
- contact: !omap
|
10
|
+
- email: <%= @url_name %>@gmail.com
|
11
|
+
- homepage: http://<%= @url_name %>.me/
|
@@ -127,6 +127,12 @@ ul {
|
|
127
127
|
width: 6em;
|
128
128
|
padding-right: 1em;
|
129
129
|
text-align: right;
|
130
|
+
vertical-align: top;
|
131
|
+
}
|
132
|
+
|
133
|
+
#quick-links li span.adr {
|
134
|
+
display: inline-block;
|
135
|
+
width: 15em;
|
130
136
|
}
|
131
137
|
|
132
138
|
#quick-links li a:hover, #quick-links li a:focus {
|
@@ -193,6 +199,14 @@ em {
|
|
193
199
|
color: #333;
|
194
200
|
}
|
195
201
|
|
202
|
+
|
203
|
+
|
204
|
+
.nick {
|
205
|
+
display: none;
|
206
|
+
}
|
207
|
+
|
208
|
+
|
209
|
+
|
196
210
|
#summarize { color: #555; position: absolute; top: 1em; right: 1em; width: 10em; }
|
197
211
|
|
198
212
|
#projects-contributed-to { margin-left: 1em; }
|
data/lib/vitae/version.rb
CHANGED
data/test/active_server_test.rb
CHANGED
@@ -4,28 +4,35 @@ require "nokogiri"
|
|
4
4
|
class ActiveServerTest < VitaeServerTestCase
|
5
5
|
|
6
6
|
test "the homepage contains lists the cvs we're hosting" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
check_includes_standard_assets
|
7
|
+
with_project :dkaz do
|
8
|
+
get '/'
|
9
|
+
assert last_response.ok?
|
10
|
+
assert_matches %w[Katya Derek Arthur Zeena]
|
11
|
+
assert_select "a[href='/katya']", "Katya"
|
12
|
+
|
13
|
+
check_includes_standard_assets
|
14
|
+
end
|
16
15
|
end
|
17
16
|
|
18
17
|
test "a CV gets its own show page" do
|
19
|
-
|
20
|
-
|
18
|
+
with_project :sals do
|
19
|
+
get '/sajal_shah'
|
20
|
+
assert last_response.ok?
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
assert_select "h1", "Sajal Shah"
|
23
|
+
assert_select "a[href='/']"
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
check_includes_standard_assets
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
test "a CV doesn't mindlessly blat out converted yaml data" do
|
30
|
+
with_project :default do
|
31
|
+
get '/arthur_gunn'
|
32
|
+
assert last_response.ok?
|
27
33
|
|
28
|
-
|
34
|
+
assert_no_select "h2", "vitae_config"
|
35
|
+
end
|
29
36
|
end
|
30
37
|
|
31
38
|
def check_includes_standard_assets theme="default"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "nokogiri"
|
3
|
+
|
4
|
+
class CvFormatTest < VitaeServerTestCase
|
5
|
+
|
6
|
+
test "a CV has a well formatted vcard" do
|
7
|
+
with_project :default do
|
8
|
+
get '/arthur_gunn'
|
9
|
+
assert last_response.ok?
|
10
|
+
|
11
|
+
assert_select ".vcard" do
|
12
|
+
assert_select "h1#title.fn", "Arthur Gunn"
|
13
|
+
assert_select "ul#quick-links li" do
|
14
|
+
assert_select "span.label", "github"
|
15
|
+
assert_select "a.url[href='https://github.com/gunn']", "github"
|
16
|
+
assert_select "span.label", "e-mail"
|
17
|
+
assert_select "a.email[href='mailto:arthur@gunn.co.nz']", "arthur@gunn.co.nz"
|
18
|
+
end
|
19
|
+
assert_select "span.adr" do
|
20
|
+
assert_select "span.street-address", "96 London St."
|
21
|
+
assert_select "span.postal-code", "9016"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'test/unit'
|
3
|
+
require "stringio"
|
3
4
|
|
4
|
-
require
|
5
|
-
require 'vitae/server/server'
|
5
|
+
require 'rubygems'
|
6
6
|
require 'rack/test'
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
require File.expand_path('../../lib/vitae', __FILE__)
|
9
|
+
require 'vitae/server/server'
|
10
|
+
require "vitae/cli"
|
10
11
|
|
11
12
|
class VitaeTestCase < Test::Unit::TestCase
|
12
13
|
def self.test name, &block
|
@@ -14,31 +15,65 @@ class VitaeTestCase < Test::Unit::TestCase
|
|
14
15
|
define_method name, &block
|
15
16
|
end
|
16
17
|
|
18
|
+
@@projects = {
|
19
|
+
:default => "",
|
20
|
+
:sals => "sajal_shah",
|
21
|
+
:dkaz => "derek katya arthur zeena"
|
22
|
+
}
|
23
|
+
|
24
|
+
@@vitae_test_dir = File.expand_path('../../tmp', __FILE__)
|
25
|
+
@@vitae_executable = File.expand_path('../../bin/vitae', __FILE__)
|
26
|
+
|
17
27
|
test "tests working here" do
|
18
28
|
# assert(true, "Just to keep t/u happy.")
|
19
29
|
end
|
20
30
|
|
21
31
|
def vitae_test_dir
|
22
|
-
FileUtils.mkdir_p
|
32
|
+
FileUtils.mkdir_p @@vitae_test_dir
|
23
33
|
end
|
24
34
|
|
25
35
|
def vitae_executable
|
26
|
-
|
36
|
+
@@vitae_executable
|
27
37
|
end
|
28
38
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
39
|
+
def vitae_server args=""
|
40
|
+
cli = Vitae::CLI.new
|
41
|
+
real_argv = ARGV
|
42
|
+
ARGV.replace(args.split(/\s+/))
|
43
|
+
get_stdout_and_stderr { cli.server }
|
44
|
+
ensure
|
45
|
+
ARGV.replace(real_argv)
|
35
46
|
end
|
36
47
|
|
37
48
|
def vitae_create args=""
|
38
49
|
FileUtils.cd vitae_test_dir
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
cli = Vitae::CLI.new
|
51
|
+
args = args.split(/\s+/)
|
52
|
+
project_name = args.first || ""
|
53
|
+
Vitae::project_root = File.join(vitae_test_dir, project_name)
|
54
|
+
|
55
|
+
get_stdout_and_stderr { cli.create(*args) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_project name, &block
|
59
|
+
project_dir = File.join(vitae_test_dir, name.to_s)
|
60
|
+
vitae_create "#{project_dir} #{@@projects[name]}" if !File.exist?( project_dir )
|
61
|
+
Vitae::project_root = project_dir
|
62
|
+
FileUtils.cd project_dir
|
63
|
+
yield
|
64
|
+
ensure
|
65
|
+
Vitae::project_root = ''
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_stdout_and_stderr &block
|
69
|
+
new_out = StringIO.new
|
70
|
+
real_stdout, $stdout = $stdout, new_out
|
71
|
+
real_stderr, $stderr = $stderr, new_out
|
72
|
+
yield
|
73
|
+
new_out.string
|
74
|
+
ensure
|
75
|
+
$stderr = real_stderr
|
76
|
+
$stdout = real_stdout
|
42
77
|
end
|
43
78
|
|
44
79
|
def clear_test_dir
|
@@ -46,7 +81,7 @@ class VitaeTestCase < Test::Unit::TestCase
|
|
46
81
|
end
|
47
82
|
|
48
83
|
def vitae_file? path
|
49
|
-
File.exist?
|
84
|
+
File.exist? File.expand_path(path, vitae_test_dir)
|
50
85
|
end
|
51
86
|
|
52
87
|
end
|
@@ -64,15 +99,43 @@ class VitaeServerTestCase < VitaeTestCase
|
|
64
99
|
end
|
65
100
|
end
|
66
101
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
102
|
+
def assert_no_select selector, content=nil
|
103
|
+
assert_select selector, content, :want_matches => false
|
104
|
+
end
|
105
|
+
|
106
|
+
def assert_select selector, content=nil, options={}
|
107
|
+
options = { :want_matches => true }.merge(options)
|
108
|
+
|
109
|
+
text = options[:text] || last_response.body
|
110
|
+
want_matches = options[:want_matches]
|
111
|
+
|
112
|
+
selector = "#{@parent_selector} #{selector}" if @parent_selector
|
113
|
+
|
114
|
+
elements = Nokogiri::HTML.parse(text).css(selector)
|
115
|
+
have_matches = elements.size>0
|
116
|
+
|
117
|
+
did_match = if content
|
118
|
+
elements.any? { |el| el.content.match content }
|
119
|
+
else
|
120
|
+
have_matches
|
121
|
+
end
|
122
|
+
|
123
|
+
if !want_matches
|
124
|
+
assert(!did_match, "Found matches for the selector '#{selector}', but we didn't want any.")
|
125
|
+
else
|
126
|
+
assert(did_match, "No matches for the selector '#{selector}'.") if !content
|
127
|
+
assert(did_match, "No matches for '#{content}' with the selector '#{selector}'.") if content
|
128
|
+
end
|
129
|
+
|
130
|
+
if block_given? && have_matches
|
131
|
+
begin
|
132
|
+
in_scope, @parent_selector = @parent_selector, selector
|
133
|
+
yield
|
134
|
+
ensure
|
135
|
+
@parent_selector = in_scope
|
73
136
|
end
|
74
|
-
assert(false, "No matches for '#{content}' with the selector '#{selector}'.")
|
75
137
|
end
|
138
|
+
|
76
139
|
end
|
77
140
|
|
78
141
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class UtilitiesTest < VitaeTestCase
|
4
|
+
|
5
|
+
test "we can capture stdout and stderr" do
|
6
|
+
captured_string = get_stdout_and_stderr do
|
7
|
+
puts "hello!"
|
8
|
+
print "h"
|
9
|
+
puts "i"
|
10
|
+
$stderr.print "bye!"
|
11
|
+
end
|
12
|
+
expected_string = "hello!\nhi\nbye!"
|
13
|
+
|
14
|
+
assert_equal(expected_string, captured_string)
|
15
|
+
end
|
16
|
+
|
17
|
+
test "yaml can be ordered" do
|
18
|
+
numbers_from_yaml = example_yaml_hash["numbers"].map { |k,v| "#{k} = #{v}" }.join("\n")
|
19
|
+
|
20
|
+
the_numbers = "one = 1\ntwo = 2\nthree = 3\nfour = 4\nfive = 5\nsix = 6"
|
21
|
+
|
22
|
+
assert_equal(the_numbers, numbers_from_yaml)
|
23
|
+
end
|
24
|
+
|
25
|
+
test "ordered hash items can be skipped" do
|
26
|
+
number_string = example_yaml_hash["numbers"].except(%w[one three four five]).map { |k,v| k }.join(", ")
|
27
|
+
assert_equal("two, six", number_string)
|
28
|
+
end
|
29
|
+
|
30
|
+
def example_yaml_hash
|
31
|
+
yaml_hash_string = %q{
|
32
|
+
--- !omap
|
33
|
+
- numbers: !omap
|
34
|
+
- one: 1
|
35
|
+
- two: 2
|
36
|
+
- three: 3
|
37
|
+
- four: 4
|
38
|
+
- five: 5
|
39
|
+
- six: 6
|
40
|
+
}
|
41
|
+
yaml_hash = YAML.load yaml_hash_string
|
42
|
+
end
|
43
|
+
|
44
|
+
test "CV.first and CV.last do their jobs" do
|
45
|
+
with_project :dkaz do
|
46
|
+
assert_equal "Arthur", CV.first.name
|
47
|
+
assert_equal "Zeena", CV.last.name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -2,11 +2,6 @@ require "test_helper"
|
|
2
2
|
|
3
3
|
class VitaeExecutableTest < VitaeTestCase
|
4
4
|
|
5
|
-
test "create gives help when called without args" do
|
6
|
-
output = vitae_create
|
7
|
-
assert_match('"create" was called incorrectly', output)
|
8
|
-
end
|
9
|
-
|
10
5
|
test "create generates a project" do
|
11
6
|
clear_test_dir
|
12
7
|
output = vitae_create("my_cvs")
|
@@ -35,8 +30,14 @@ class VitaeExecutableTest < VitaeTestCase
|
|
35
30
|
end
|
36
31
|
end
|
37
32
|
|
38
|
-
test "the server
|
39
|
-
|
33
|
+
test "the server tells us how many CVs it's serving, where and how" do
|
34
|
+
with_project :dkaz do
|
35
|
+
assert_match("Serving 4 CVs at http://0.0.0.0:3141/ from dkaz\n", vitae_server("--pretend"))
|
36
|
+
end
|
37
|
+
|
38
|
+
with_project :sals do
|
39
|
+
assert_match("Serving 1 CV at http://0.0.0.0:3141/ from sals\n", vitae_server("--pretend"))
|
40
|
+
end
|
40
41
|
end
|
41
42
|
|
42
43
|
test "the server accepts a custom port" do
|
@@ -45,8 +46,13 @@ class VitaeExecutableTest < VitaeTestCase
|
|
45
46
|
assert_match(":1121", vitae_server("--pretend -p 1121"))
|
46
47
|
end
|
47
48
|
|
48
|
-
test "the server gives rackup help" do
|
49
|
-
|
50
|
-
end
|
49
|
+
# test "the server gives rackup help" do
|
50
|
+
# assert_match("Usage: rackup", vitae_server("-h"))
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# test "create gives help when called without args" do
|
54
|
+
# output = vitae_create
|
55
|
+
# assert_match('"create" was called incorrectly', output)
|
56
|
+
# end
|
51
57
|
|
52
58
|
end
|
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. Under heavy development,
|
13
|
+
s.description = %q{Vitae is to CVs what rubygems is to ruby code. Under heavy development, very under development.}
|
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,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vitae
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 2
|
10
|
+
version: 0.1.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Arthur Gunn
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-12-
|
18
|
+
date: 2010-12-08 00:00:00 +13:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -82,7 +82,7 @@ dependencies:
|
|
82
82
|
version: 1.4.0
|
83
83
|
type: :development
|
84
84
|
version_requirements: *id004
|
85
|
-
description: Vitae is to CVs what rubygems is to ruby code. Under heavy development,
|
85
|
+
description: Vitae is to CVs what rubygems is to ruby code. Under heavy development, very under development.
|
86
86
|
email:
|
87
87
|
- arthur@gunn.co.nz
|
88
88
|
executables:
|
@@ -101,13 +101,17 @@ files:
|
|
101
101
|
- bin/vitae
|
102
102
|
- config.ru
|
103
103
|
- lib/vitae.rb
|
104
|
+
- lib/vitae/cli.rb
|
104
105
|
- lib/vitae/cv.rb
|
106
|
+
- lib/vitae/ordered_hash.rb
|
105
107
|
- lib/vitae/server/.gitignore
|
108
|
+
- lib/vitae/server/assets/favicon.ico
|
106
109
|
- lib/vitae/server/helpers.rb
|
107
110
|
- lib/vitae/server/server.rb
|
108
111
|
- lib/vitae/server/views/index.haml
|
109
112
|
- lib/vitae/server/views/layout.haml
|
110
113
|
- lib/vitae/server/views/show.haml
|
114
|
+
- lib/vitae/server/views/vcard.haml
|
111
115
|
- lib/vitae/templates/cvs/arthur_gunn.yaml
|
112
116
|
- lib/vitae/templates/cvs/default.yaml.tt
|
113
117
|
- lib/vitae/templates/themes/default/application.css
|
@@ -115,8 +119,10 @@ files:
|
|
115
119
|
- lib/vitae/version.rb
|
116
120
|
- test/active_server_test.rb
|
117
121
|
- test/basic_server_test.rb
|
122
|
+
- test/cv_format_test.rb
|
118
123
|
- test/tag_helpers_test.rb
|
119
124
|
- test/test_helper.rb
|
125
|
+
- test/utilities_test.rb
|
120
126
|
- test/vitae_executable_test.rb
|
121
127
|
- vitae.gemspec
|
122
128
|
has_rdoc: true
|
@@ -156,6 +162,8 @@ summary: A structured CV publishing system.
|
|
156
162
|
test_files:
|
157
163
|
- test/active_server_test.rb
|
158
164
|
- test/basic_server_test.rb
|
165
|
+
- test/cv_format_test.rb
|
159
166
|
- test/tag_helpers_test.rb
|
160
167
|
- test/test_helper.rb
|
168
|
+
- test/utilities_test.rb
|
161
169
|
- test/vitae_executable_test.rb
|