utopia 1.9.11 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +3 -2
- data/.gitignore +4 -1
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -1
- data/README.md +2 -2
- data/Rakefile +10 -10
- data/benchmarks/call_vs_check.rb +36 -0
- data/benchmarks/const_vs_hash.rb +33 -0
- data/documentation/Gemfile +5 -0
- data/documentation/Guardfile +20 -0
- data/documentation/config.ru +6 -13
- data/documentation/config/puma.rb +20 -0
- data/documentation/pages/_editor.xnode +64 -0
- data/documentation/pages/_heading.xnode +2 -2
- data/documentation/pages/_page.xnode +1 -2
- data/documentation/pages/errors/exception.xnode +3 -3
- data/documentation/pages/errors/file-not-found.xnode +3 -3
- data/documentation/pages/wiki/bower-integration/content.md +1 -1
- data/documentation/pages/wiki/content.md +6 -8
- data/documentation/pages/wiki/controller.rb +3 -3
- data/documentation/pages/wiki/edit.xnode +7 -19
- data/documentation/pages/wiki/middleware/content/content.md +4 -10
- data/documentation/pages/wiki/{controller → middleware/controller}/actions/content.md +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/links.yaml +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/rewrite/content.md +3 -3
- data/documentation/pages/wiki/show.xnode +4 -6
- data/documentation/pages/wiki/updating-utopia/content.md +55 -0
- data/documentation/pages/wiki/your-first-page/content.md +5 -3
- data/documentation/public/materials +1 -0
- data/lib/utopia.rb +3 -4
- data/lib/utopia/command.rb +4 -284
- data/lib/utopia/command/server.rb +115 -0
- data/lib/utopia/command/setup.rb +78 -0
- data/lib/utopia/command/site.rb +183 -0
- data/lib/utopia/content.rb +83 -59
- data/lib/utopia/content/{transaction.rb → document.rb} +116 -110
- data/lib/utopia/content/link.rb +7 -2
- data/lib/utopia/content/links.rb +2 -1
- data/lib/utopia/content/markup.rb +7 -2
- data/lib/utopia/{tags/deferred.rb → content/namespace.rb} +25 -6
- data/lib/utopia/content/node.rb +74 -76
- data/lib/utopia/content/response.rb +22 -3
- data/lib/utopia/content/tags.rb +66 -0
- data/lib/utopia/controller.rb +10 -18
- data/lib/utopia/controller/actions.rb +10 -0
- data/lib/utopia/controller/base.rb +2 -1
- data/lib/utopia/controller/respond.rb +1 -1
- data/lib/utopia/controller/rewrite.rb +8 -4
- data/lib/utopia/exceptions.rb +1 -0
- data/lib/utopia/exceptions/handler.rb +7 -2
- data/lib/utopia/exceptions/mailer.rb +33 -12
- data/lib/utopia/{tags/node.rb → extensions/array_split.rb} +11 -9
- data/lib/utopia/{tags/environment.rb → extensions/date_comparisons.rb} +24 -14
- data/lib/utopia/http.rb +2 -0
- data/lib/utopia/locale.rb +1 -0
- data/lib/utopia/localization.rb +37 -28
- data/lib/utopia/logger.rb +1 -0
- data/lib/utopia/logger/compact_formatter.rb +1 -0
- data/lib/utopia/middleware.rb +11 -1
- data/lib/utopia/path.rb +1 -0
- data/lib/utopia/path/matcher.rb +14 -2
- data/lib/utopia/redirection.rb +13 -16
- data/lib/utopia/session.rb +14 -6
- data/lib/utopia/setup.rb +3 -1
- data/lib/utopia/static.rb +11 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/server/git/hooks/post-receive +0 -4
- data/setup/site/.gitignore +9 -0
- data/setup/site/.rspec +1 -0
- data/setup/site/Gemfile +4 -0
- data/setup/site/Guardfile +17 -0
- data/setup/site/Rakefile +2 -2
- data/setup/site/config.ru +5 -12
- data/setup/site/pages/_heading.xnode +2 -2
- data/setup/site/pages/_page.xnode +1 -1
- data/setup/site/pages/errors/exception.xnode +3 -3
- data/setup/site/pages/errors/file-not-found.xnode +3 -3
- data/setup/site/pages/welcome/index.xnode +3 -3
- data/setup/site/public/_static/site.css +4 -0
- data/setup/site/spec/spec_helper.rb +29 -0
- data/setup/site/tasks/deploy.rake +13 -0
- data/setup/site/tasks/development.rake +34 -0
- data/setup/site/tasks/environment.rake +17 -0
- data/spec/mock_node.rb +15 -0
- data/spec/spec_helper.rb +29 -0
- data/{lib/utopia/extensions/date.rb → spec/utopia/content/document_spec.rb} +31 -21
- data/spec/utopia/content/markup_spec.rb +2 -2
- data/spec/utopia/content/{tag_spec.rb → namespace_spec.rb} +17 -10
- data/spec/utopia/content/tags_spec.rb +80 -0
- data/spec/utopia/content_spec.rb +1 -1
- data/spec/utopia/content_spec.ru +1 -6
- data/spec/utopia/content_spec/_heading.xnode +1 -1
- data/spec/utopia/content_spec/content/test-partial.xnode +1 -1
- data/spec/utopia/content_spec/index.xnode +1 -1
- data/spec/utopia/controller/middleware_spec.ru +1 -3
- data/spec/utopia/controller/respond_spec.rb +2 -22
- data/spec/utopia/controller/respond_spec.ru +1 -5
- data/spec/utopia/controller/respond_spec/errors/file-not-found.xnode +7 -6
- data/spec/utopia/exceptions/handler_spec.ru +1 -2
- data/spec/utopia/exceptions/mailer_spec.ru +1 -2
- data/spec/utopia/extensions_spec.rb +2 -2
- data/spec/utopia/localization_spec.ru +1 -2
- data/spec/utopia/performance_spec.rb +2 -6
- data/spec/utopia/performance_spec/config.ru +5 -12
- data/spec/utopia/performance_spec/pages/_heading.xnode +2 -2
- data/spec/utopia/performance_spec/pages/_page.xnode +1 -1
- data/spec/utopia/performance_spec/pages/errors/exception.xnode +3 -3
- data/spec/utopia/performance_spec/pages/errors/file-not-found.xnode +3 -3
- data/spec/utopia/performance_spec/pages/welcome/index.xnode +3 -3
- data/spec/utopia/setup_spec.rb +79 -15
- data/utopia.gemspec +3 -3
- metadata +41 -27
- data/.simplecov +0 -9
- data/documentation/pages/welcome/index.xnode +0 -41
- data/lib/utopia/content/tag.rb +0 -90
- data/lib/utopia/extensions/array.rb +0 -29
- data/lib/utopia/tags/override.rb +0 -33
- data/setup/site/.simplecov +0 -9
- data/setup/site/tasks/test.rake +0 -10
- data/setup/site/tasks/utopia.rake +0 -41
- data/spec/utopia/controller/respond_spec/rewrite/controller.rb +0 -12
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
# THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
require_relative 'setup'
|
|
22
|
+
|
|
23
|
+
module Utopia
|
|
24
|
+
module Command
|
|
25
|
+
# Server setup commands.
|
|
26
|
+
class Server < Samovar::Command
|
|
27
|
+
# Create a server.
|
|
28
|
+
class Create < Samovar::Command
|
|
29
|
+
self.description = "Create a remote Utopia website suitable for deployment using git."
|
|
30
|
+
|
|
31
|
+
def invoke(parent)
|
|
32
|
+
destination_root = parent.root
|
|
33
|
+
|
|
34
|
+
FileUtils.mkdir_p File.join(destination_root, "public")
|
|
35
|
+
|
|
36
|
+
Update.new.invoke(parent)
|
|
37
|
+
|
|
38
|
+
# Print out helpful git remote add message:
|
|
39
|
+
hostname = `hostname`.chomp
|
|
40
|
+
puts "Now add the git remote to your local repository:\n\tgit remote add production ssh://#{hostname}#{destination_root}"
|
|
41
|
+
puts "Then push to it:\n\tgit push --set-upstream production master"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Update a server.
|
|
46
|
+
class Update < Samovar::Command
|
|
47
|
+
self.description = "Update the git hooks in an existing server repository."
|
|
48
|
+
|
|
49
|
+
def invoke(parent)
|
|
50
|
+
destination_root = parent.root
|
|
51
|
+
|
|
52
|
+
Dir.chdir(destination_root) do
|
|
53
|
+
# It's okay to call this on an existing repo, it will only update config as required to enable --shared.
|
|
54
|
+
# --shared allows multiple users to access the site with the same group.
|
|
55
|
+
system("git", "init", "--shared") or fail "could not initialize repository"
|
|
56
|
+
|
|
57
|
+
system("git", "config", "receive.denyCurrentBranch", "ignore") or fail "could not set configuration"
|
|
58
|
+
system("git", "config", "core.worktree", destination_root) or fail "could not set configuration"
|
|
59
|
+
|
|
60
|
+
# In theory, to convert from non-shared to shared:
|
|
61
|
+
# chgrp -R <group-name> . # Change files and directories' group
|
|
62
|
+
# chmod -R g+w . # Change permissions
|
|
63
|
+
# chmod g-w .git/objects/pack/* # Git pack files should be immutable
|
|
64
|
+
# chmod g+s `find . -type d` # New files get group id of directory
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Setup::Server.update_default_environment(destination_root)
|
|
68
|
+
|
|
69
|
+
# Copy git hooks:
|
|
70
|
+
system("cp", "-r", File.join(Setup::Server::ROOT, 'git', 'hooks'), File.join(destination_root, '.git')) or fail "could not copy git hooks"
|
|
71
|
+
# finally set everything in the .git directory to be group writable
|
|
72
|
+
system("chmod", "-Rf", "g+w", File.join(destination_root, '.git')) or fail "could not update permissions of .git directory"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set environment variables within the server deployment.
|
|
77
|
+
class Environment < Samovar::Command
|
|
78
|
+
self.description = "Update environment variables in config/environment.yaml"
|
|
79
|
+
|
|
80
|
+
many :variables, "A list of environment KEY=VALUE pairs to set."
|
|
81
|
+
|
|
82
|
+
def invoke(parent)
|
|
83
|
+
return if variables.empty?
|
|
84
|
+
|
|
85
|
+
destination_root = parent.root
|
|
86
|
+
|
|
87
|
+
Setup::Server.environment(destination_root) do |store|
|
|
88
|
+
variables.each do |variable|
|
|
89
|
+
key, value = variable.split('=', 2)
|
|
90
|
+
|
|
91
|
+
if value
|
|
92
|
+
puts "ENV[#{key.inspect}] will default to #{value.inspect} unless otherwise specified."
|
|
93
|
+
store[key] = value
|
|
94
|
+
else
|
|
95
|
+
puts "ENV[#{key.inspect}] will be unset unless otherwise specified."
|
|
96
|
+
store.delete(key)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
self.description = "Manage server deployments."
|
|
104
|
+
|
|
105
|
+
nested '<command>',
|
|
106
|
+
'create' => Create,
|
|
107
|
+
'update' => Update,
|
|
108
|
+
'environment' => Environment
|
|
109
|
+
|
|
110
|
+
def invoke(parent)
|
|
111
|
+
@command.invoke(parent)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
# THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
require 'fileutils'
|
|
22
|
+
require 'find'
|
|
23
|
+
|
|
24
|
+
require 'yaml/store'
|
|
25
|
+
|
|
26
|
+
require 'samovar'
|
|
27
|
+
require 'securerandom'
|
|
28
|
+
|
|
29
|
+
module Utopia
|
|
30
|
+
module Command
|
|
31
|
+
# The command for client/server setup.
|
|
32
|
+
module Setup
|
|
33
|
+
# This path must point to utopia/setup in the gem source.
|
|
34
|
+
BASE = File.expand_path("../../../setup", __dir__)
|
|
35
|
+
|
|
36
|
+
# Helpers for setting up a local site.
|
|
37
|
+
module Site
|
|
38
|
+
# Configuration files which should be installed/updated:
|
|
39
|
+
CONFIGURATION_FILES = ['.bowerrc', '.gitignore', 'config.ru', 'config/environment.rb', 'Gemfile', 'Guardfile', 'Rakefile', 'tasks/bower.rake', 'tasks/deploy.rake', 'tasks/development.rake', 'tasks/environment.rake', 'tasks/log.rake']
|
|
40
|
+
|
|
41
|
+
# Directories that should exist:
|
|
42
|
+
DIRECTORIES = ["config", "lib", "pages", "public", "tasks"]
|
|
43
|
+
|
|
44
|
+
# Directories that should be removed during upgrade process:
|
|
45
|
+
OLD_PATHS = ["access_log", "cache", "tmp", 'tasks/test.rake', 'tasks/utopia.rake']
|
|
46
|
+
|
|
47
|
+
# The root directory of the template site:
|
|
48
|
+
ROOT = File.join(BASE, 'site')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Helpers for setting up the server deployment.
|
|
52
|
+
module Server
|
|
53
|
+
# The root directory of the template server deployment:
|
|
54
|
+
ROOT = File.join(BASE, 'server')
|
|
55
|
+
|
|
56
|
+
# Setup `config/environment.yaml` according to specified options.
|
|
57
|
+
def self.environment(root)
|
|
58
|
+
environment_path = File.join(root, 'config/environment.yaml')
|
|
59
|
+
FileUtils.mkpath File.dirname(environment_path)
|
|
60
|
+
|
|
61
|
+
store = YAML::Store.new(environment_path)
|
|
62
|
+
|
|
63
|
+
store.transaction do
|
|
64
|
+
yield store
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Set some useful defaults for the environment.
|
|
69
|
+
def self.update_default_environment(root)
|
|
70
|
+
environment(root) do |store|
|
|
71
|
+
store['RACK_ENV'] ||= 'production'
|
|
72
|
+
store['UTOPIA_SESSION_SECRET'] ||= SecureRandom.hex(40)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
# THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
require_relative 'setup'
|
|
22
|
+
|
|
23
|
+
module Utopia
|
|
24
|
+
module Command
|
|
25
|
+
# Local site setup commands.
|
|
26
|
+
class Site < Samovar::Command
|
|
27
|
+
# Create a local site.
|
|
28
|
+
class Create < Samovar::Command
|
|
29
|
+
self.description = "Create a new local Utopia website using the default template."
|
|
30
|
+
# self.example = "utopia --in www.example.com site create"
|
|
31
|
+
|
|
32
|
+
def invoke(parent)
|
|
33
|
+
destination_root = parent.root
|
|
34
|
+
|
|
35
|
+
$stderr.puts "Setting up initial site in #{destination_root} for Utopia v#{Utopia::VERSION}..."
|
|
36
|
+
|
|
37
|
+
Setup::Site::DIRECTORIES.each do |directory|
|
|
38
|
+
FileUtils.mkdir_p(File.join(destination_root, directory))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Find.find(Setup::Site::ROOT) do |source_path|
|
|
42
|
+
# What is this doing?
|
|
43
|
+
destination_path = File.join(destination_root, source_path[Setup::Site::ROOT.size..-1])
|
|
44
|
+
|
|
45
|
+
if File.directory?(source_path)
|
|
46
|
+
FileUtils.mkdir_p(destination_path)
|
|
47
|
+
else
|
|
48
|
+
unless File.exist? destination_path
|
|
49
|
+
FileUtils.copy_entry(source_path, destination_path)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Setup::Site::CONFIGURATION_FILES.each do |configuration_file|
|
|
55
|
+
destination_path = File.join(destination_root, configuration_file)
|
|
56
|
+
|
|
57
|
+
buffer = File.read(destination_path).gsub('$UTOPIA_VERSION', Utopia::VERSION)
|
|
58
|
+
|
|
59
|
+
File.open(destination_path, "w") { |file| file.write(buffer) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Dir.chdir(destination_root) do
|
|
63
|
+
puts "Setting up site in #{destination_root}..."
|
|
64
|
+
|
|
65
|
+
if `which bundle`.strip != ''
|
|
66
|
+
puts "Generating initial package list with bundle..."
|
|
67
|
+
system("bundle", "install", "--binstubs") or fail "could not install bundled gems"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if `which git`.strip == ""
|
|
71
|
+
$stderr.puts "Now is a good time to learn about git: http://git-scm.com/"
|
|
72
|
+
elsif !File.exist?('.git')
|
|
73
|
+
puts "Setting up git repository..."
|
|
74
|
+
system("git", "init") or fail "could not create git repository"
|
|
75
|
+
system("git", "add", ".") or fail "could not add all files"
|
|
76
|
+
system("git", "commit", "-q", "-m", "Initial Utopia v#{Utopia::VERSION} site.") or fail "could not commit files"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
name = `git config user.name || whoami`.chomp
|
|
81
|
+
|
|
82
|
+
puts
|
|
83
|
+
puts " #{name},".ljust(78)
|
|
84
|
+
puts "Thank you for using Utopia!".center(78)
|
|
85
|
+
puts "We sincerely hope that Utopia helps to".center(78)
|
|
86
|
+
puts "make your life easier and more enjoyable.".center(78)
|
|
87
|
+
puts ""
|
|
88
|
+
puts "To start the development server, run:".center(78)
|
|
89
|
+
puts "rake server".center(78)
|
|
90
|
+
puts ""
|
|
91
|
+
puts "For extreme productivity, please consult the online documentation".center(78)
|
|
92
|
+
puts "https://github.com/ioquatix/utopia".center(78)
|
|
93
|
+
puts " ~ Samuel. ".rjust(78)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Update a local site.
|
|
98
|
+
class Update < Samovar::Command
|
|
99
|
+
self.description = "Upgrade an existing site to use the latest configuration files from the template."
|
|
100
|
+
|
|
101
|
+
# Move legacy `pages/_static` to `public/_static`.
|
|
102
|
+
def move_static!
|
|
103
|
+
if File.lstat("public/_static").symlink?
|
|
104
|
+
FileUtils.rm_f "public/_static"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if File.directory?("pages/_static") and !File.exist?("public/_static")
|
|
108
|
+
system("git", "mv", "pages/_static", "public/")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def invoke(parent)
|
|
113
|
+
destination_root = parent.root
|
|
114
|
+
branch_name = "utopia-upgrade-#{Utopia::VERSION}"
|
|
115
|
+
|
|
116
|
+
$stderr.puts "Upgrading #{destination_root}..."
|
|
117
|
+
|
|
118
|
+
Dir.chdir(destination_root) do
|
|
119
|
+
system('git', 'checkout', '-b', branch_name) or fail "could not change branch"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Setup::Site::DIRECTORIES.each do |directory|
|
|
123
|
+
FileUtils.mkdir_p(File.join(destination_root, directory))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Setup::Site::OLD_PATHS.each do |path|
|
|
127
|
+
path = File.join(destination_root, path)
|
|
128
|
+
$stderr.puts "\tRemoving #{path}..."
|
|
129
|
+
FileUtils.rm_rf(path)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
Setup::Site::CONFIGURATION_FILES.each do |configuration_file|
|
|
133
|
+
source_path = File.join(Setup::Site::ROOT, configuration_file)
|
|
134
|
+
destination_path = File.join(destination_root, configuration_file)
|
|
135
|
+
|
|
136
|
+
$stderr.puts "Updating #{destination_path}..."
|
|
137
|
+
|
|
138
|
+
FileUtils.copy_entry(source_path, destination_path)
|
|
139
|
+
buffer = File.read(destination_path).gsub('$UTOPIA_VERSION', Utopia::VERSION)
|
|
140
|
+
File.open(destination_path, "w") { |file| file.write(buffer) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
begin
|
|
144
|
+
Dir.chdir(destination_root) do
|
|
145
|
+
# Stage any files that have been changed or removed:
|
|
146
|
+
system("git", "add", "-u") or fail "could not add files"
|
|
147
|
+
|
|
148
|
+
# Stage any new files that we have explicitly added:
|
|
149
|
+
system("git", "add", *Setup::Site::CONFIGURATION_FILES) or fail "could not add files"
|
|
150
|
+
|
|
151
|
+
move_static!
|
|
152
|
+
|
|
153
|
+
# Commit all changes:
|
|
154
|
+
system("git", "commit", "-m", "Upgrade to utopia #{Utopia::VERSION}.") or fail "could not commit changes"
|
|
155
|
+
|
|
156
|
+
# Checkout master..
|
|
157
|
+
system("git", "checkout", "master") or fail "could not checkout master"
|
|
158
|
+
|
|
159
|
+
# and merge:
|
|
160
|
+
system("git", "merge", "--squash", "--no-commit", branch_name) or fail "could not merge changes"
|
|
161
|
+
end
|
|
162
|
+
rescue RuntimeError
|
|
163
|
+
$stderr.puts "** Detected error with upgrade, reverting changes. Some new files may still exist in tree. **"
|
|
164
|
+
|
|
165
|
+
system("git", "checkout", "master")
|
|
166
|
+
ensure
|
|
167
|
+
system("git", "branch", "-D", branch_name)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
nested '<command>',
|
|
173
|
+
'create' => Create,
|
|
174
|
+
'update' => Update
|
|
175
|
+
|
|
176
|
+
self.description = "Manage local utopia sites."
|
|
177
|
+
|
|
178
|
+
def invoke(parent)
|
|
179
|
+
@command.invoke(parent)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
data/lib/utopia/content.rb
CHANGED
|
@@ -23,94 +23,65 @@ require_relative 'localization'
|
|
|
23
23
|
|
|
24
24
|
require_relative 'content/node'
|
|
25
25
|
require_relative 'content/markup'
|
|
26
|
+
require_relative 'content/tags'
|
|
26
27
|
|
|
27
28
|
require 'trenni/template'
|
|
28
29
|
|
|
29
30
|
require 'concurrent/map'
|
|
30
31
|
|
|
31
32
|
module Utopia
|
|
33
|
+
# A middleware which serves dynamically generated content based on markup files.
|
|
32
34
|
class Content
|
|
33
35
|
INDEX = 'index'.freeze
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
CONTENT_NAMESPACE = 'content'.freeze
|
|
38
|
+
UTOPIA_NAMESPACE = 'utopia'.freeze
|
|
39
|
+
DEFERRED_TAG_NAME = 'utopia:deferred'.freeze
|
|
40
|
+
CONTENT_TAG_NAME = 'utopia:content'.freeze
|
|
41
|
+
|
|
42
|
+
# @param root [String] The content root where pages will be generated from.
|
|
43
|
+
# @param namespaces [Hash<String,Library>] Tag namespaces for dynamic tag lookup.
|
|
44
|
+
def initialize(app, root: Utopia::default_root, namespaces: {})
|
|
36
45
|
@app = app
|
|
46
|
+
@root = root
|
|
37
47
|
|
|
38
|
-
@
|
|
48
|
+
@template_cache = Concurrent::Map.new
|
|
49
|
+
@node_cache = Concurrent::Map.new
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
@template_cache = Concurrent::Map.new
|
|
42
|
-
else
|
|
43
|
-
@template_cache = nil
|
|
44
|
-
end
|
|
51
|
+
@namespaces = namespaces
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
# Default content namespace for dynamic path based lookup:
|
|
54
|
+
@namespaces[CONTENT_NAMESPACE] ||= self.method(:content_tag)
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
# The core namespace for utopia specific functionality:
|
|
57
|
+
@namespaces[UTOPIA_NAMESPACE] ||= Tags
|
|
49
58
|
end
|
|
50
59
|
|
|
51
60
|
def freeze
|
|
61
|
+
return self if frozen?
|
|
62
|
+
|
|
52
63
|
@root.freeze
|
|
53
|
-
@
|
|
64
|
+
@namespaces.values.each(&:freeze)
|
|
65
|
+
@namespaces.freeze
|
|
54
66
|
|
|
55
67
|
super
|
|
56
68
|
end
|
|
57
|
-
|
|
69
|
+
|
|
58
70
|
attr :root
|
|
59
|
-
|
|
71
|
+
|
|
60
72
|
def fetch_template(path)
|
|
61
|
-
|
|
62
|
-
@template_cache.fetch_or_store(path.to_s) do
|
|
63
|
-
Trenni::MarkupTemplate.load_file(path)
|
|
64
|
-
end
|
|
65
|
-
else
|
|
73
|
+
@template_cache.fetch_or_store(path.to_s) do
|
|
66
74
|
Trenni::MarkupTemplate.load_file(path)
|
|
67
75
|
end
|
|
68
76
|
end
|
|
69
77
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
name = Path.create(name)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
if Path === name
|
|
77
|
-
name = parent_path + name
|
|
78
|
-
name_path = name.components.dup
|
|
79
|
-
name_path[-1] += XNODE_EXTENSION
|
|
80
|
-
else
|
|
81
|
-
name_path = name + XNODE_EXTENSION
|
|
82
|
-
end
|
|
78
|
+
# Look up a named tag such as `<entry />` or `<content:page>...`
|
|
79
|
+
def lookup_tag(qualified_name, node)
|
|
80
|
+
namespace, name = Trenni::Tag.split(qualified_name)
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
while components.any?
|
|
87
|
-
tag_path = File.join(root, components, name_path)
|
|
88
|
-
|
|
89
|
-
if File.exist? tag_path
|
|
90
|
-
return Node.new(self, Path[components] + name, parent_path + name, tag_path)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if String === name_path
|
|
94
|
-
tag_path = File.join(root, components, '_' + name_path)
|
|
95
|
-
|
|
96
|
-
if File.exist? tag_path
|
|
97
|
-
return Node.new(self, Path[components] + name, parent_path + name, tag_path)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
components.pop
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
return nil
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Look up a named tag such as <entry />
|
|
108
|
-
def lookup_tag(name, parent_path)
|
|
109
|
-
if @tags.key? name
|
|
110
|
-
return @tags[name]
|
|
82
|
+
if library = @namespaces[namespace]
|
|
83
|
+
library.call(name, node)
|
|
111
84
|
end
|
|
112
|
-
|
|
113
|
-
return fetch_tag(name, parent_path)
|
|
114
85
|
end
|
|
115
86
|
|
|
116
87
|
# The request_path is an absolute uri path, e.g. /foo/bar. If an xnode file exists on disk for this exact path, it is instantiated, otherwise nil.
|
|
@@ -155,5 +126,58 @@ module Utopia
|
|
|
155
126
|
|
|
156
127
|
return @app.call(env)
|
|
157
128
|
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def lookup_content(name, parent_path)
|
|
133
|
+
if String === name && name.index('/')
|
|
134
|
+
name = Path.create(name)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if Path === name
|
|
138
|
+
name = parent_path + name
|
|
139
|
+
name_path = name.components.dup
|
|
140
|
+
name_path[-1] += XNODE_EXTENSION
|
|
141
|
+
else
|
|
142
|
+
name_path = name + XNODE_EXTENSION
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
components = parent_path.components.dup
|
|
146
|
+
|
|
147
|
+
while components.any?
|
|
148
|
+
tag_path = File.join(@root, components, name_path)
|
|
149
|
+
|
|
150
|
+
if File.exist? tag_path
|
|
151
|
+
return Node.new(self, Path[components] + name, parent_path + name, tag_path)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if String === name_path
|
|
155
|
+
tag_path = File.join(@root, components, '_' + name_path)
|
|
156
|
+
|
|
157
|
+
if File.exist? tag_path
|
|
158
|
+
return Node.new(self, Path[components] + name, parent_path + name, tag_path)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
components.pop
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
return nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def content_tag(name, node)
|
|
169
|
+
parent_path = node.parent_path
|
|
170
|
+
|
|
171
|
+
# If the current node is called 'foo', we can't lookup 'foo' in the current directory or we will have infinite recursion.
|
|
172
|
+
if name == node.name
|
|
173
|
+
parent_path = parent_path.dirname
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
cache_key = parent_path + name
|
|
177
|
+
|
|
178
|
+
@node_cache.fetch_or_store(cache_key) do
|
|
179
|
+
lookup_content(name, parent_path)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
158
182
|
end
|
|
159
183
|
end
|