vitae 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|