lapis_lazuli 2.0.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|