lapis_lazuli 2.0.1 → 3.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 +5 -5
- data/README.md +1 -1
- data/lapis_lazuli.gemspec +10 -8
- data/lib/lapis_lazuli/api.rb +1 -1
- data/lib/lapis_lazuli/argparse.rb +1 -1
- data/lib/lapis_lazuli/browser.rb +37 -61
- data/lib/lapis_lazuli/browser/error.rb +89 -62
- data/lib/lapis_lazuli/browser/find.rb +1 -2
- data/lib/lapis_lazuli/cli.rb +1 -1
- data/lib/lapis_lazuli/cucumber.rb +1 -1
- data/lib/lapis_lazuli/generators/cucumber.rb +1 -1
- data/lib/lapis_lazuli/generators/cucumber/template/README.md +2 -0
- data/lib/lapis_lazuli/generators/cucumber/template/config/config.yml +6 -21
- data/lib/lapis_lazuli/generators/cucumber/template/config/cucumber.yml +42 -13
- data/lib/lapis_lazuli/generators/cucumber/template/config/users.yml +21 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/1_basic.feature +49 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/2_account.feature +38 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/3_todo_list.feature +23 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/authentication_helper.rb +122 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/navigation_helper.rb +64 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/registration_helper.rb +102 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/user_helper.rb +74 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/account_steps.rb +60 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/basic_steps.rb +70 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/todo_steps.rb +27 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/support/env.rb +3 -2
- data/lib/lapis_lazuli/generic/xpath.rb +1 -1
- data/lib/lapis_lazuli/options.rb +3 -2
- data/lib/lapis_lazuli/placeholders.rb +1 -1
- data/lib/lapis_lazuli/proxy.rb +1 -1
- data/lib/lapis_lazuli/runtime.rb +1 -1
- data/lib/lapis_lazuli/scenario.rb +1 -1
- data/lib/lapis_lazuli/storage.rb +1 -1
- data/lib/lapis_lazuli/version.rb +2 -2
- data/lib/lapis_lazuli/versions.rb +1 -1
- data/lib/lapis_lazuli/world/config.rb +348 -334
- data/lib/lapis_lazuli/world/hooks.rb +85 -84
- data/lib/lapis_lazuli/world/logging.rb +1 -1
- data/test/Gemfile +2 -16
- data/test/config/config.yml +7 -6
- data/test/config/cucumber.yml +6 -8
- data/test/features/bindings.feature +1 -1
- data/test/features/browser.feature +1 -1
- data/test/features/step_definitions/interaction_steps.rb +5 -2
- data/test/features/step_definitions/validation_steps.rb +2 -2
- data/test/features/support/env.rb +21 -1
- data/test/results/latest_results.json +0 -0
- metadata +74 -28
- data/lib/lapis_lazuli/generators/cucumber/template/features/account.feature +0 -26
- data/lib/lapis_lazuli/generators/cucumber/template/features/example.feature +0 -30
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/interaction_steps.rb +0 -165
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/precondition_steps.rb +0 -63
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/validation_steps.rb +0 -67
- data/lib/lapis_lazuli/generators/cucumber/template/features/support/functions.rb +0 -68
@@ -0,0 +1,74 @@
|
|
1
|
+
# This helper loads user data from the config files.
|
2
|
+
# After loading the data, it will overwrite certain strings, like __TIMESTAMP__ to randomize information
|
3
|
+
module User
|
4
|
+
extend LapisLazuli
|
5
|
+
|
6
|
+
class << self
|
7
|
+
@@data = nil
|
8
|
+
|
9
|
+
def load_user_data(user)
|
10
|
+
data = config('users.default-user')
|
11
|
+
begin
|
12
|
+
specific_data = config("users.#{user}")
|
13
|
+
rescue Exception => err1
|
14
|
+
begin
|
15
|
+
specific_data = config("users.#{ENV['TEST_ENV']}.#{user}")
|
16
|
+
rescue Exception => err2
|
17
|
+
error "The given user `#{user}` was not found in any of the config files:\n- #{err1.message}\n- #{err2.message}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
new_data = data.merge specific_data
|
21
|
+
@@data = replace_hash_constants(new_data)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(field)
|
25
|
+
return @@data[field]
|
26
|
+
end
|
27
|
+
|
28
|
+
def set(field, value)
|
29
|
+
@@data[field] = User.replace_constants(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Replace random or time values of a complete hash
|
33
|
+
def replace_hash_constants(hash)
|
34
|
+
if hash.respond_to? :each
|
35
|
+
new_hash = {}
|
36
|
+
hash.each do |key, value|
|
37
|
+
new_hash[key] = replace_constants(value)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
new_hash = replace_constants(hash)
|
41
|
+
end
|
42
|
+
return new_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
# replace certain constants in a string, for example '_TIMESTAMP_' becomes '154875631'
|
46
|
+
def replace_constants(value)
|
47
|
+
if value.to_s == value
|
48
|
+
epoch = Time.now.to_i
|
49
|
+
alpha = number_to_letter(epoch)
|
50
|
+
timestamp = Time.now.strftime("D%Y-%M-%d-T%H-%M-%S")
|
51
|
+
|
52
|
+
old_val = value.to_s
|
53
|
+
value = value.sub('_RAND_', epoch.to_s)
|
54
|
+
value = value.sub('_TIMESTAMP_', timestamp)
|
55
|
+
value = value.sub('_RAND-ALPHA_', alpha)
|
56
|
+
unless value == old_val
|
57
|
+
log.debug "#{old_val} > #{value}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
return value
|
61
|
+
end
|
62
|
+
|
63
|
+
def number_to_letter(numbers)
|
64
|
+
num_string = numbers.to_s
|
65
|
+
alpha26 = ("a".."j").to_a
|
66
|
+
letters = ''
|
67
|
+
num_string.scan(/./).each do |number|
|
68
|
+
letters += alpha26[number.to_i]
|
69
|
+
end
|
70
|
+
return letters
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# A step definition is a regex, to learn more about this go to http://rubular.com/
|
2
|
+
# More info: https://github.com/cucumber/cucumber/wiki/Step-Definitions
|
3
|
+
|
4
|
+
# The following step definition accepts both:
|
5
|
+
# - the user logs in > will use the last stored user data
|
6
|
+
# - "user-x" logs in > will load user data from config.yml
|
7
|
+
When(/^"?(.*?)"? logs in$/) do |user|
|
8
|
+
user = nil if user == 'the user'
|
9
|
+
# Check out ./features/helpers/ for the function being called
|
10
|
+
Auth.log_in(user)
|
11
|
+
end
|
12
|
+
|
13
|
+
When(/^the user clicks on the logout button$/) do
|
14
|
+
Auth.log_out
|
15
|
+
end
|
16
|
+
|
17
|
+
Given(/^the user is logged out$/) do
|
18
|
+
Auth.ensure_log_out
|
19
|
+
end
|
20
|
+
|
21
|
+
Given(/^"(.*?)" is logged in$/) do |user|
|
22
|
+
Auth.ensure_log_in(user)
|
23
|
+
end
|
24
|
+
|
25
|
+
# One step definition that handles both the logged in as the logged out state
|
26
|
+
Then(/^the page should display as logged (in|out) state$/) do |logged|
|
27
|
+
# Adjust variable for checking logged in or logged out state.
|
28
|
+
if logged == 'in' and !Auth.is_logged_in?
|
29
|
+
error 'Unable to find profile picture, the user wasnt logged in successfully'
|
30
|
+
elsif logged == 'out' and Auth.is_logged_in?
|
31
|
+
error 'The profile picture is present, indicating that the user did not log out successfully'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# A static way to write your step definition
|
36
|
+
When 'the user clicks on the registration button' do
|
37
|
+
Register.open_registration
|
38
|
+
end
|
39
|
+
|
40
|
+
When 'the registration form should display' do
|
41
|
+
error 'The registration form did not display.' unless Register.is_registration_open?
|
42
|
+
end
|
43
|
+
|
44
|
+
Given /^"(.*?)" has the registration form opened$/ do |user|
|
45
|
+
User.load_user_data(user)
|
46
|
+
Register.ensure_open_registrarion
|
47
|
+
end
|
48
|
+
|
49
|
+
Given /^"(.*?)" has registered a new account$/ do |user|
|
50
|
+
Register.ensure_registered(user)
|
51
|
+
end
|
52
|
+
|
53
|
+
When 'the user completes registration' do
|
54
|
+
Register.register_user
|
55
|
+
end
|
56
|
+
|
57
|
+
Then 'the successful registration message should display' do
|
58
|
+
result, message = Register.registration_result
|
59
|
+
error message unless result
|
60
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
|
3
|
+
# Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
|
4
|
+
# Author: "<%= config[:user] %>" <<%= config[:email] %>>
|
5
|
+
|
6
|
+
# interactions_steps.rb is used to interact with elements on the page.
|
7
|
+
# Quite advances piece of regex. Goto http://rubular.com/ for practise
|
8
|
+
# Whatever is put between parenthesis is captured as a variable unless you start with `?:`
|
9
|
+
# /(?: text)?/ means: ` text` is optional and do not capture it as a variable
|
10
|
+
# /(.*?)/ means: Any amount of any character, but don't be "greedy"
|
11
|
+
# "greedy" means, do not take characters that match the rest of the regex.
|
12
|
+
Given(/^the user navigates to (?:the )?"(.*?)"(?: page)?$/) do |page|
|
13
|
+
Nav.to(page)
|
14
|
+
end
|
15
|
+
|
16
|
+
# An example of interacting with some elements
|
17
|
+
Given(/^the user searches for "(.*?)"$/) do |query|
|
18
|
+
# Get the input element
|
19
|
+
searchbox = browser.find(:text_field => {:name => "s"})
|
20
|
+
# Make sure the input field is empty
|
21
|
+
searchbox.clear rescue log.debug "Could not clear searchbox"
|
22
|
+
# Fill in the query
|
23
|
+
searchbox.send_keys(query)
|
24
|
+
# Press enter to submit the search
|
25
|
+
searchbox.send_keys(:enter)
|
26
|
+
end
|
27
|
+
|
28
|
+
When(/^the user scrolls down$/) do
|
29
|
+
browser.driver.execute_script("window.scrollBy(0,400)")
|
30
|
+
end
|
31
|
+
|
32
|
+
Then(/^text "([^"]*)" should display somewhere on the page$/) do |string|
|
33
|
+
# Search for the text on the page
|
34
|
+
browser.wait(:xpath => "//*[contains(text(),\"#{string}\")]")
|
35
|
+
end
|
36
|
+
|
37
|
+
When(/^the user clicks on link "(.*?)"$/) do |url|
|
38
|
+
# Search for the element that includes the expected text
|
39
|
+
|
40
|
+
browser.wait(
|
41
|
+
:like => {
|
42
|
+
:element => :a,
|
43
|
+
:attribute => :href,
|
44
|
+
:include => url
|
45
|
+
}
|
46
|
+
).click
|
47
|
+
end
|
48
|
+
|
49
|
+
When(/^the user clicks on the spritecloud logo$/) do
|
50
|
+
# Search for the logo
|
51
|
+
logo = browser.find(
|
52
|
+
a: {class: ['logo']},
|
53
|
+
message: 'Unable to find the logo on this page.'
|
54
|
+
)
|
55
|
+
# And click the logo
|
56
|
+
logo.click
|
57
|
+
end
|
58
|
+
|
59
|
+
Then(/^the user should be on page "(.*?)"$/) do |page|
|
60
|
+
url = Nav.set_url page
|
61
|
+
Nav.wait_for_url url
|
62
|
+
end
|
63
|
+
|
64
|
+
Then /^the text "(.*?)" should display on the blog page$/ do |expected_text|
|
65
|
+
# Many things wrong here, can you fix it?
|
66
|
+
header = browser.find(:like => [:h2, :id, 'entry_title'])
|
67
|
+
unless heeder.text.include? expected_text
|
68
|
+
error "Unable to find text `#{expected_text}`"
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
When(/^a todo item with text "(.*?)" is added$/) do |string|
|
2
|
+
pending # Write code here that turns the phrase above into concrete actions
|
3
|
+
end
|
4
|
+
|
5
|
+
Then(/^a todo item with text "(.*?)" should be present$/) do |string|
|
6
|
+
pending # Write code here that turns the phrase above into concrete actions
|
7
|
+
end
|
8
|
+
|
9
|
+
Given(/^"(.*?)" has (at least|exactly|at most) (\d+) todo items?$/) do |user, rule, number|
|
10
|
+
pending # Write code here that turns the phrase above into concrete actions
|
11
|
+
end
|
12
|
+
|
13
|
+
When(/^the user marks (\d+|all) todo items as completed$/) do |amount|
|
14
|
+
pending # Write code here that turns the phrase above into concrete actions
|
15
|
+
end
|
16
|
+
|
17
|
+
When("the clear completed button is pressed") do
|
18
|
+
pending # Write code here that turns the phrase above into concrete actions
|
19
|
+
end
|
20
|
+
|
21
|
+
Then("no todo items should display") do
|
22
|
+
pending # Write code here that turns the phrase above into concrete actions
|
23
|
+
end
|
24
|
+
|
25
|
+
Then("the progress bar should display at {int} percent") do |int|
|
26
|
+
pending # Write code here that turns the phrase above into concrete actions
|
27
|
+
end
|
@@ -5,13 +5,14 @@
|
|
5
5
|
require 'lapis_lazuli'
|
6
6
|
require 'lapis_lazuli/cucumber'
|
7
7
|
|
8
|
-
LapisLazuli::WorldModule::Config.
|
8
|
+
LapisLazuli::WorldModule::Config.add_config("config/config.yml")
|
9
|
+
LapisLazuli::WorldModule::Config.add_config("config/users.yml")
|
9
10
|
World(LapisLazuli)
|
10
11
|
|
11
12
|
# Do something when LapisLazuli is started (This is before the browser is opened)
|
12
13
|
LapisLazuli.Start do
|
13
14
|
#If BROWSER is NIL, Lapis Lazuli will default to Firefox
|
14
|
-
if
|
15
|
+
if ENV['BROWSER'] == 'firefox'
|
15
16
|
|
16
17
|
# Get Selenium to create a profile object
|
17
18
|
require 'selenium-webdriver'
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
module LapisLazuli
|
data/lib/lapis_lazuli/options.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
module LapisLazuli
|
@@ -13,10 +13,11 @@ module LapisLazuli
|
|
13
13
|
"error_strings" => [nil, "List of strings that indicate errors when detected on a web page."],
|
14
14
|
"default_env" => [nil, "Indicates which environment specific configuration to load when no test environment is provided explicitly."],
|
15
15
|
"test_env" => [nil, "Indicates which environment specific configuration to load in this test run."],
|
16
|
-
"browser" => [
|
16
|
+
"browser" => [nil, "Indicates the browser in which to run tests. Possible values are 'firefox', 'chrome', 'safari', 'ie', 'ios'."],
|
17
17
|
"email_domain" => ["google.com", "The domain name used when generating email addresses. See the `placeholders` command for more information."],
|
18
18
|
"screenshot_on_failure" => [true, "Toggle whether failed scenarios should result in a screenshot being taken automatically."],
|
19
19
|
"screenshot_dir" => [".#{File::SEPARATOR}screenshots", "Location prefix for the screenshot path."],
|
20
|
+
"screenshots_height" => [nil, "When 'full' the window will be resized to max height before taking a screenshot"],
|
20
21
|
"screenshot_scheme" => ["old", "Naming scheme for screenshots. Possible values are 'old' and 'new'. This option will be deprecated in the near future, and only the new scheme will be supported."],
|
21
22
|
"breakpoint_on_error" => [false, "If the error() function is used to create errors, should the debugger be started?"],
|
22
23
|
"step_pause_time" => [0, "(Deprecated) Number of seconds to wait after each cucumber step is executed."],
|
data/lib/lapis_lazuli/proxy.rb
CHANGED
data/lib/lapis_lazuli/runtime.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
require 'singleton'
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
require "securerandom"
|
data/lib/lapis_lazuli/storage.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
module LapisLazuli
|
data/lib/lapis_lazuli/version.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
module LapisLazuli
|
9
|
-
VERSION = "
|
9
|
+
VERSION = "3.0.0"
|
10
10
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# LapisLazuli
|
3
3
|
# https://github.com/spriteCloud/lapis-lazuli
|
4
4
|
#
|
5
|
-
# Copyright (c) 2013-
|
5
|
+
# Copyright (c) 2013-2019 spriteCloud B.V. and other LapisLazuli contributors.
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
require 'lapis_lazuli/api'
|
@@ -9,193 +9,204 @@
|
|
9
9
|
require "lapis_lazuli/options"
|
10
10
|
require "lapis_lazuli/storage"
|
11
11
|
require "lapis_lazuli/runtime"
|
12
|
+
require 'deep_merge'
|
12
13
|
|
13
14
|
module LapisLazuli
|
14
|
-
module WorldModule
|
15
|
-
##
|
16
|
-
# Module with configuration loading related functions
|
17
|
-
#
|
18
|
-
# Manages the following:
|
19
|
-
# @config - internal configuration representation
|
20
|
-
# config_file - Needs to be set before config can be accessed.
|
21
|
-
# @env - loaded/detected config/test environment
|
22
|
-
module Config
|
15
|
+
module WorldModule
|
23
16
|
##
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
17
|
+
# Module with configuration loading related functions
|
18
|
+
#
|
19
|
+
# Manages the following:
|
20
|
+
# @config - internal configuration representation
|
21
|
+
# config_files - Needs to be set before config can be accessed.
|
22
|
+
# @env - loaded/detected config/test environment
|
23
|
+
module Config
|
24
|
+
##
|
25
|
+
# Explicitly store the configuration file name.
|
26
|
+
module ClassMethods
|
27
|
+
|
28
|
+
# <b>DEPRECATED:</b> Please use <tt>add_config</tt> instead.
|
29
|
+
def config_file=(name)
|
30
|
+
warn "[DEPRECATION] `config_file = name` is deprecated. Please use `add_config(file)` instead."
|
31
|
+
add_config(name)
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
# <b>DEPRECATED:</b> Please use <tt>config_files</tt> instead.
|
35
|
+
def config_file
|
36
|
+
warn "[DEPRECATION] `config_file` is deprecated. Please use `config_files` instead."
|
37
|
+
return config_files
|
38
|
+
end
|
35
39
|
|
40
|
+
def add_config(file)
|
41
|
+
@config_files = [] if @config_files.nil?
|
42
|
+
@config_files.push(file)
|
43
|
+
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# still work to overwrite an existing configuration.
|
41
|
-
def init
|
42
|
-
# Guard against doing this more than once.
|
43
|
-
if not @config.nil?
|
44
|
-
return
|
45
|
-
end
|
46
|
-
|
47
|
-
if Config.config_file.nil?
|
48
|
-
raise "No configuration file provided, set LapisLazuli::WorldModule::Config.config_file"
|
45
|
+
def config_files
|
46
|
+
return @config_files || ["config/config.yml"]
|
47
|
+
end
|
49
48
|
end
|
49
|
+
extend ClassMethods
|
50
50
|
|
51
|
-
load_config(Config.config_file)
|
52
|
-
# In case there was no config file found an empty @config needs to be set to prevent infinite looping.
|
53
|
-
if @config.nil?
|
54
|
-
warn 'Unable to find a configuration file, defaulting to empty config.yml.'
|
55
|
-
@config = {}
|
56
|
-
end
|
57
|
-
|
58
|
-
@metadata = Runtime.instance.set_if(self, :metadata) do
|
59
|
-
log.debug "Creating metadata storage"
|
60
|
-
Storage.new("metadata")
|
61
|
-
end
|
62
|
-
end
|
63
51
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
52
|
+
##
|
53
|
+
# The configuration is not a singleton, precisely, but it does not need to
|
54
|
+
# be created more than once. Note that explicitly calling load_config will
|
55
|
+
# still work to overwrite an existing configuration.
|
56
|
+
def init
|
57
|
+
# Guard against doing this more than once.
|
58
|
+
unless @config.nil?
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
if Config.config_files.nil?
|
63
|
+
raise "No configuration file provided, set LapisLazuli::WorldModule::Config.config_files"
|
64
|
+
end
|
70
65
|
|
66
|
+
load_config(Config.config_files)
|
67
|
+
# In case there was no config file found an empty @config needs to be set to prevent infinite looping.
|
68
|
+
if @config.nil?
|
69
|
+
warn 'Unable to find a configuration file, defaulting to empty config.yml.'
|
70
|
+
@config = {}
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
#
|
77
|
-
# Example:
|
78
|
-
# ENV['TEST_ENV'] = 'production'
|
79
|
-
# load_config("config/config.yml")
|
80
|
-
#
|
81
|
-
# Will try to load the following files:
|
82
|
-
# - config/config-production.yml
|
83
|
-
# - config/config-debug.yml
|
84
|
-
# - config/config-test.yml
|
85
|
-
# - config/config-local.yml
|
86
|
-
# - config/config.yml
|
87
|
-
def load_config(config_name)
|
88
|
-
# Split the filename
|
89
|
-
ext = File.extname(config_name)
|
90
|
-
dir, filename = File.split(config_name)
|
91
|
-
basename = File.basename(filename, ext)
|
92
|
-
|
93
|
-
# What are the suffixes to check
|
94
|
-
suffixes = [
|
95
|
-
"debug",
|
96
|
-
"test",
|
97
|
-
"local"
|
98
|
-
]
|
99
|
-
|
100
|
-
if ENV["TEST_ENV"]
|
101
|
-
@env = ENV["TEST_ENV"]
|
73
|
+
@metadata = Runtime.instance.set_if(self, :metadata) do
|
74
|
+
log.debug "Creating metadata storage"
|
75
|
+
Storage.new("metadata")
|
76
|
+
end
|
102
77
|
end
|
103
78
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
79
|
+
def metadata
|
80
|
+
if @metadata.nil?
|
81
|
+
raise "No metadata available"
|
82
|
+
end
|
83
|
+
return @metadata
|
108
84
|
end
|
109
85
|
|
110
|
-
# Turn suffixes into files to try
|
111
|
-
files = []
|
112
|
-
suffixes.each do |suffix|
|
113
|
-
files << "#{dir}#{File::SEPARATOR}#{basename}-#{suffix}#{ext}"
|
114
|
-
end
|
115
|
-
files << config_name
|
116
86
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
87
|
+
##
|
88
|
+
# Loads a config based on a filename
|
89
|
+
#
|
90
|
+
# Supports: YML, JSON
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
# ENV['TEST_ENV'] = 'production'
|
94
|
+
# load_config("config/config.yml")
|
95
|
+
#
|
96
|
+
# Will try to load the following files:
|
97
|
+
# - config/config-production.yml
|
98
|
+
# - config/config-debug.yml
|
99
|
+
# - config/config-test.yml
|
100
|
+
# - config/config-local.yml
|
101
|
+
# - config/config.yml
|
102
|
+
#
|
103
|
+
# Throws errors if:
|
104
|
+
# - Config file isn't readable
|
105
|
+
# - Environment doesn't exist in config
|
106
|
+
# - Default environment not set in config if no environment is set
|
107
|
+
def load_config(config_names)
|
108
|
+
# Go trough each config_name
|
109
|
+
config_names.each do |config_name|
|
110
|
+
files = []
|
111
|
+
# Split the filename
|
112
|
+
ext = File.extname(config_name)
|
113
|
+
dir, filename = File.split(config_name)
|
114
|
+
basename = File.basename(filename, ext)
|
115
|
+
|
116
|
+
# What are the suffixes to check
|
117
|
+
suffixes = %w(debug test local)
|
118
|
+
|
119
|
+
if ENV["TEST_ENV"]
|
120
|
+
@env = ENV["TEST_ENV"]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Do we have an environment
|
124
|
+
unless @env.nil?
|
125
|
+
# Add it to the suffixes
|
126
|
+
suffixes.unshift(@env)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Turn suffixes into files to try
|
130
|
+
suffixes.each do |suffix|
|
131
|
+
files << "#{dir}#{File::SEPARATOR}#{basename}-#{suffix}#{ext}"
|
132
|
+
end
|
133
|
+
files << config_name
|
134
|
+
# Try all files in order
|
135
|
+
files.each do |file|
|
136
|
+
# Check if files exist
|
137
|
+
if File.file?(file)
|
138
|
+
begin
|
139
|
+
# Try to load a config file
|
140
|
+
self.add_config_from_file(file)
|
141
|
+
break
|
142
|
+
rescue Exception => e
|
143
|
+
raise e
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
# If we have an environment, the config should contain it
|
149
|
+
if not @env.nil? and not self.has_config?(@env)
|
150
|
+
raise "Environment `#{@env}` doesn't exist in any of the config files"
|
151
|
+
end
|
152
|
+
|
153
|
+
# If we don't have one then load the default
|
154
|
+
if @env.nil? and self.has_config?("default_env")
|
155
|
+
tmp = self.config("default_env")
|
156
|
+
if self.has_config?(tmp)
|
157
|
+
@env = tmp
|
158
|
+
ENV['TEST_ENV'] = tmp
|
159
|
+
else
|
160
|
+
raise "Default environment not present in any of the config files"
|
126
161
|
end
|
127
162
|
end
|
128
163
|
end
|
129
|
-
end
|
130
164
|
|
131
165
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
# Set the global @config variable
|
143
|
-
@config = get_config_from_file(filename)
|
144
|
-
|
145
|
-
# If we have an environment this config should have it
|
146
|
-
if not @env.nil? and not self.has_config?(@env)
|
147
|
-
raise "Environment `#{@env}` doesn't exist in config file"
|
166
|
+
##
|
167
|
+
# Loads a config file
|
168
|
+
#
|
169
|
+
# Supports: YML, JSON
|
170
|
+
#
|
171
|
+
# Adds the possibility to merge multiple config files.
|
172
|
+
def add_config_from_file(filename)
|
173
|
+
@config = {} if @config.nil?
|
174
|
+
# Add the data to the global config
|
175
|
+
@config.deep_merge! get_config_from_file(filename)
|
148
176
|
end
|
149
177
|
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
# returns the data that's loaded from a config file.
|
168
|
-
# Supports YAML and JSON
|
169
|
-
def get_config_from_file(filename)
|
170
|
-
# Try to load the file from disk
|
171
|
-
begin
|
172
|
-
# Determine the extension
|
173
|
-
ext = File.extname(filename)
|
174
|
-
# Use the correct loader
|
175
|
-
if ext == ".yml"
|
176
|
-
data = YAML.load_file(filename)
|
177
|
-
elsif ext == ".json"
|
178
|
-
json = File.read(filename)
|
179
|
-
data = JSON.parse(json)
|
178
|
+
# returns the data that's loaded from a config file.
|
179
|
+
# Supports YAML and JSON
|
180
|
+
def get_config_from_file(filename)
|
181
|
+
# Try to load the file from disk
|
182
|
+
begin
|
183
|
+
# Determine the extension
|
184
|
+
ext = File.extname(filename)
|
185
|
+
# Use the correct loader
|
186
|
+
if ext == ".yml"
|
187
|
+
data = YAML.load_file(filename)
|
188
|
+
elsif ext == ".json"
|
189
|
+
json = File.read(filename)
|
190
|
+
data = JSON.parse(json)
|
191
|
+
end
|
192
|
+
rescue Exception => e
|
193
|
+
raise "Error loading file: #{filename} #{e}"
|
180
194
|
end
|
181
|
-
rescue Exception => e
|
182
|
-
raise "Error loading file: #{filename} #{e}"
|
183
|
-
end
|
184
195
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
196
|
+
# Fix up empty files
|
197
|
+
if data.nil? or data == false
|
198
|
+
warn "Could not load configuration from '#{Config.config_files}'; it might be empty or malformed."
|
199
|
+
data = {}
|
200
|
+
end
|
201
|
+
return data
|
189
202
|
end
|
190
|
-
return data
|
191
|
-
end
|
192
203
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
204
|
+
##
|
205
|
+
# Does the config have a variable?
|
206
|
+
# Uses config and catches any errors it raises
|
207
|
+
def has_config?(variable)
|
208
|
+
# Make sure the configured configuration is loaded, if possible
|
209
|
+
init
|
199
210
|
|
200
211
|
begin
|
201
212
|
value = self.config(variable)
|
@@ -203,220 +214,223 @@ module WorldModule
|
|
203
214
|
rescue RuntimeError => err
|
204
215
|
return false
|
205
216
|
end
|
206
|
-
end
|
207
|
-
|
208
|
-
##
|
209
|
-
# Get the configuration from the config,
|
210
|
-
# uses a dot seperator for object traversing
|
211
|
-
#
|
212
|
-
# Example:
|
213
|
-
# ll.config("test.google.url") => "www.google.com"
|
214
|
-
#
|
215
|
-
# Raises error if traversing the object is impossible
|
216
|
-
def config(variable=false, default=(no_default_set=true;nil))
|
217
|
-
# Make sure the configured configuration is loaded, if possible
|
218
|
-
init
|
219
|
-
|
220
|
-
# No variable given? Return the entire object.
|
221
|
-
result = @config
|
222
|
-
if not variable
|
223
|
-
return result
|
224
217
|
end
|
225
218
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
219
|
+
##
|
220
|
+
# Get the configuration from the config,
|
221
|
+
# uses a dot seperator for object traversing
|
222
|
+
#
|
223
|
+
# Example:
|
224
|
+
# ll.config("test.google.url") => "www.google.com"
|
225
|
+
#
|
226
|
+
# Raises error if traversing the object is impossible
|
227
|
+
def config(variable=false, default=(no_default_set=true; nil))
|
228
|
+
# Make sure the configured configuration is loaded, if possible
|
229
|
+
init
|
230
|
+
|
231
|
+
# No variable given? Return the entire object.
|
232
|
+
result = @config
|
233
|
+
if not variable
|
234
|
+
return result
|
231
235
|
end
|
232
|
-
end
|
233
236
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
237
|
+
# Environment variables for known options override the option.
|
238
|
+
if CONFIG_OPTIONS.has_key? variable
|
239
|
+
var = variable.upcase
|
240
|
+
if ENV.has_key? var
|
241
|
+
return ENV[var]
|
242
|
+
end
|
238
243
|
end
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
244
|
+
|
245
|
+
# Otherwise try to find it in the configuration object
|
246
|
+
variable.split(".").each do |part|
|
247
|
+
if no_default_set == true && result.nil?
|
248
|
+
raise "Unknown configuration variable '#{variable}' and no default given!"
|
249
|
+
end
|
250
|
+
break if result.nil?
|
251
|
+
begin
|
252
|
+
result = result[part]
|
253
|
+
rescue TypeError, NoMethodError => ex
|
254
|
+
warn "Could not read configuration variable #{variable}: #{ex}"
|
255
|
+
break
|
256
|
+
end
|
245
257
|
end
|
246
|
-
end
|
247
258
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
259
|
+
if default.nil? and result.nil?
|
260
|
+
if CONFIG_OPTIONS.has_key? variable
|
261
|
+
return CONFIG_OPTIONS[variable][0]
|
262
|
+
elsif no_default_set == true
|
263
|
+
raise "Unknown configuration variable '#{variable}' and no default given!"
|
264
|
+
end
|
265
|
+
else
|
266
|
+
return result || default
|
253
267
|
end
|
254
|
-
else
|
255
|
-
return result || default
|
256
268
|
end
|
257
|
-
end
|
258
269
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
270
|
+
##
|
271
|
+
# Does the environment have a certain config variable
|
272
|
+
def has_env?(variable)
|
273
|
+
# Make sure the configured configuration is loaded, if possible
|
274
|
+
init
|
264
275
|
|
265
|
-
|
266
|
-
|
276
|
+
if @env.nil?
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
return self.has_config?("#{@env}.#{variable}")
|
267
280
|
end
|
268
|
-
return self.has_config?("#{@env}.#{variable}")
|
269
|
-
end
|
270
|
-
|
271
|
-
##
|
272
|
-
# Returns current environment
|
273
|
-
def current_env
|
274
|
-
init
|
275
281
|
|
276
|
-
|
277
|
-
|
282
|
+
##
|
283
|
+
# Returns current environment
|
284
|
+
def current_env
|
285
|
+
init
|
278
286
|
|
279
|
-
|
280
|
-
# Get a environment variable from the config file
|
281
|
-
# Alias for ll.config(ll.env + "." + variable)
|
282
|
-
def env(variable=false, default=(no_default_set=true;nil))
|
283
|
-
# Make sure the configured configuration is loaded, if possible
|
284
|
-
init
|
285
|
-
|
286
|
-
if not variable
|
287
|
-
return self.config(@env)
|
287
|
+
return @env
|
288
288
|
end
|
289
289
|
|
290
|
-
|
291
|
-
#
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
result = self.config("#{@env}.#{variable}",default)
|
290
|
+
##
|
291
|
+
# Get a environment variable from the config file
|
292
|
+
# Alias for ll.config(ll.env + "." + variable)
|
293
|
+
def env(variable=false, default=(no_default_set=true; nil))
|
294
|
+
# Make sure the configured configuration is loaded, if possible
|
295
|
+
init
|
298
296
|
|
299
|
-
|
300
|
-
|
301
|
-
|
297
|
+
if not variable
|
298
|
+
return self.config(@env)
|
299
|
+
end
|
302
300
|
|
303
|
-
|
301
|
+
# Environment variables for known options override environment specific
|
302
|
+
# options, too
|
303
|
+
env_var = var_from_env(variable, default)
|
304
|
+
if env_var != default
|
305
|
+
return env_var
|
306
|
+
end
|
304
307
|
|
305
|
-
|
308
|
+
result = self.config("#{@env}.#{variable}", default)
|
306
309
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
# Make sure the configured configuration is loaded, if possible
|
311
|
-
init
|
310
|
+
if no_default_set == true and result.nil?
|
311
|
+
raise "Unknown environment variable '#{@env}.#{variable}' and no default given"
|
312
|
+
end
|
312
313
|
|
313
|
-
|
314
|
-
end
|
314
|
+
return result
|
315
315
|
|
316
|
-
##
|
317
|
-
# Get a variable from the config
|
318
|
-
# First checks the environment section, before it checks the global part
|
319
|
-
def env_or_config(variable, default=nil)
|
320
|
-
# Make sure the configured configuration is loaded, if possible
|
321
|
-
init
|
322
|
-
|
323
|
-
# Environment variables for known options override environment specific
|
324
|
-
# options, too
|
325
|
-
env_var = var_from_env(variable, default)
|
326
|
-
if env_var != default
|
327
|
-
return env_var
|
328
316
|
end
|
329
317
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
318
|
+
##
|
319
|
+
# Checks if a variabl exist in the env or config
|
320
|
+
def has_env_or_config?(variable)
|
321
|
+
# Make sure the configured configuration is loaded, if possible
|
322
|
+
init
|
323
|
+
|
324
|
+
return self.has_env?(variable) || self.has_config?(variable)
|
336
325
|
end
|
337
|
-
end
|
338
326
|
|
339
|
-
|
340
|
-
#
|
341
|
-
|
342
|
-
|
327
|
+
##
|
328
|
+
# Get a variable from the config
|
329
|
+
# First checks the environment section, before it checks the global part
|
330
|
+
def env_or_config(variable, default=(no_default_set=true; nil))
|
331
|
+
# Make sure the configured configuration is loaded, if possible
|
332
|
+
init
|
333
|
+
|
334
|
+
# Environment variables for known options override environment specific
|
335
|
+
# options, too
|
336
|
+
env_var = var_from_env(variable, default)
|
337
|
+
if env_var != default
|
338
|
+
return env_var
|
339
|
+
end
|
340
|
+
|
341
|
+
if self.has_env?(variable)
|
342
|
+
return self.env(variable)
|
343
|
+
elsif self.has_config?(variable)
|
344
|
+
return self.config(variable)
|
345
|
+
else
|
346
|
+
if no_default_set == true
|
347
|
+
raise "Unknown environment or configuration variable '(#{@env}.)#{variable}' and no default given"
|
348
|
+
end
|
349
|
+
return default
|
350
|
+
end
|
343
351
|
end
|
344
352
|
|
345
|
-
|
353
|
+
def var_from_env(var, default=nil)
|
354
|
+
# Simple solution for single depth variables like "browser"
|
355
|
+
if ENV.has_key? var
|
356
|
+
return ENV[var]
|
357
|
+
end
|
358
|
+
|
359
|
+
value = default
|
346
360
|
|
347
|
-
|
348
|
-
|
349
|
-
|
361
|
+
# Variables like:
|
362
|
+
# var_from_env("remote.url","http://test.test")
|
363
|
+
if var.is_a? String and
|
350
364
|
not default.is_a? Hash
|
351
365
|
|
352
|
-
|
353
|
-
|
366
|
+
# Env variables cannot contain a . replace them by _
|
367
|
+
key_wanted = var.gsub(".", "__")
|
354
368
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
369
|
+
# Do a case insensitive compare
|
370
|
+
ENV.keys.each do |key|
|
371
|
+
if key.casecmp(key_wanted) == 0
|
372
|
+
value = ENV[key]
|
373
|
+
break
|
374
|
+
end
|
360
375
|
end
|
361
|
-
end
|
362
376
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
377
|
+
# Environment:
|
378
|
+
# REMOTE__USER=test
|
379
|
+
# REMOTE__PASS=test
|
380
|
+
# REMOTE__PROXY__HTTP=http://test.com
|
381
|
+
#
|
382
|
+
# Call:
|
383
|
+
# var_from_env("remote",{})
|
384
|
+
#
|
385
|
+
# Result:
|
386
|
+
# {"USER" => "test",
|
387
|
+
# "PASS" => "test",
|
388
|
+
# "proxy" => {"HTTP" => "http://test.con"}}
|
389
|
+
elsif default.is_a? Hash
|
390
|
+
# Env variables cannot contain a . replace them by _
|
391
|
+
key_wanted = var.gsub(".", "__")
|
392
|
+
# Use a regular expression starting with the wanted key
|
393
|
+
rgx = Regexp.new("^#{key_wanted}", "i")
|
394
|
+
|
395
|
+
result = {}
|
396
|
+
# For each key check if it matched the regexp
|
397
|
+
ENV.keys.each do |key|
|
398
|
+
if (key =~ rgx) == 0
|
399
|
+
tmp = result
|
400
|
+
# Remove start and split into parts
|
401
|
+
parts = key.sub(rgx, "").split("__")
|
402
|
+
# Remove empty start if needed
|
403
|
+
if parts[0].to_s.empty?
|
404
|
+
parts.shift
|
405
|
+
end
|
392
406
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
407
|
+
# For each part
|
408
|
+
parts.each_with_index do |part, index|
|
409
|
+
# Final part should store the value in the hash
|
410
|
+
if index == parts.length - 1
|
411
|
+
tmp[part] = ENV[key]
|
412
|
+
else
|
413
|
+
# Otherwise, downcase the partname
|
414
|
+
part.downcase!
|
415
|
+
# Set it to an object if needed
|
416
|
+
if !tmp.has_key? part
|
417
|
+
tmp[part] = {}
|
418
|
+
end
|
419
|
+
# Assign tmp to the new hash
|
420
|
+
tmp = tmp[part]
|
404
421
|
end
|
405
|
-
# Assign tmp to the new hash
|
406
|
-
tmp = tmp[part]
|
407
422
|
end
|
408
423
|
end
|
409
424
|
end
|
410
|
-
end
|
411
425
|
|
412
|
-
|
413
|
-
|
414
|
-
|
426
|
+
# If we have set keys in the result return it
|
427
|
+
if result.keys.length > 0
|
428
|
+
return result
|
429
|
+
end
|
415
430
|
end
|
416
|
-
end
|
417
431
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
end # module WorldModule
|
432
|
+
return value
|
433
|
+
end
|
434
|
+
end # module Config
|
435
|
+
end # module WorldModule
|
422
436
|
end # module LapisLazuli
|