library/sequencer/src/modules/Sequencer.rb
# ***************************************************************************
#
# Copyright (c) 2002 - 2012 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#
# ***************************************************************************
# File: modules/Sequencer.ycp
# Module: yast2
# Summary: Wizard Sequencer
# Authors: Michal Svec <msvec@suse.cz>
# Flags: Stable
#
# $Id$
#
# This is an implementation of the wizard sequencer, the tool for
# processing workflows of dialogs.
# <br>
# All errors are reported to y2log, so if anything is unfunctional
# look to the y2log.
require "yast"
module Yast
class SequencerClass < Module
def main
textdomain "base"
@docheck = true
end
# Test (run) all dialogs in the aliases map
# @param [Hash] aliases the map of aliases
# @return returned values of tested dialogs
# @see WS documentation for the format of aliases map
def WS_testall(aliases)
aliases = deep_copy(aliases)
Builtins.maplist(aliases) { |_id, func| Builtins.eval(func) }
end
# Check correct types in maps and alias presence for sequence.
# @param [Hash] aliases the map of aliases
# @param [Hash] sequence the sequence of dialogs
# @return check passed?
def WS_check(aliases, sequence)
aliases = deep_copy(aliases)
sequence = deep_copy(sequence)
ret = []
# check if aliases is not a nil
if aliases.nil?
Builtins.y2error(2, "sequencer check: aliases is nil")
return false
end
# check if sequence is not a nil
if sequence.nil?
Builtins.y2error(2, "sequencer check: sequence is nil")
return false
end
# check if ws_start is in aliases
if Ops.get(aliases, "ws_start")
Builtins.y2error(2, "sequencer check: ws_start cannot be an alias name")
ret = Builtins.add(ret, false)
else
ret = Builtins.add(ret, true)
end
# check aliases map types
ret0 = Builtins.maplist(aliases) do |key, val|
if !Ops.is_string?(key)
Builtins.y2error(2, "sequencer check: not a string: %1", key)
next false
elsif Ops.is_list?(val)
if Ops.less_than(Builtins.size(Convert.to_list(val)), 2)
Builtins.y2error(
2,
"sequencer check: list size too small: %1 (key: %2)",
Builtins.size(Convert.to_list(val)),
key
)
next false
# FIXME: use function pointers
# else if (!is(select((list) val, 0, nil), term)) {
# y2error(2, "sequencer check: not a term: %1", select((list) val, 0, nil));
# return false;
# }
elsif !Ops.is_boolean?(Ops.get(Convert.to_list(val), 1))
Builtins.y2error(
2,
"sequencer check: not a boolean: %1",
Ops.get(Convert.to_list(val), 1)
)
next false
else
next true
end
else
# FIXME: use function pointers
# else if (!is(val, term)) {
# y2error(2, "sequencer check: not a term: %1", val);
# return false;
# }
next true
end
end
ret = Builtins.flatten([ret, ret0])
# check if ws_start is in sequence
if Ops.get(sequence, "ws_start").nil?
Builtins.y2error(2, "sequencer check: ws_start needs to be defined")
ret = Builtins.add(ret, false)
else
ret = Builtins.add(ret, true)
end
# check all aliases in sequence
ret0 = Builtins.maplist(sequence) do |key, val|
if key == "ws_start"
next true if Ops.is_symbol?(val) || !Ops.get(aliases, val).nil?
Builtins.y2error(2, "sequencer check: alias not found: %1", val)
false
elsif Ops.get(aliases, key).nil?
Builtins.y2error(2, "sequencer check: alias not found: %1", key)
false
elsif !Ops.is_map?(val)
Builtins.y2error(2, "sequencer check: not a map: %1 %2", key, val)
false
else
ret1 = Builtins.maplist(Convert.to_map(val)) do |k, v|
if !Ops.is_symbol?(k)
Builtins.y2error(2, "sequencer check: not a symbol: %1", k)
false
elsif !Ops.is_symbol?(v) && Ops.get(aliases, v).nil?
Builtins.y2error(2, "sequencer check: alias not found: %1", v)
false
else
true
end
end
ret1.all?
end
end
ret = Builtins.flatten([ret, ret0])
# check that all aliases are used
ret0 = Builtins.maplist(aliases) do |key, _val|
if !Builtins.haskey(sequence, key)
Builtins.y2warning(2, "sequencer check: alias not used: %1", key)
# return false;
end
true
end
ret = Builtins.flatten([ret, ret0])
ret.all? { |v| v }
end
# Report error and return nil
# @param [String] error the error message text
# @return always nil
# @see bug #6474
def WS_error(error)
Builtins.y2error(1, "sequencer: %1", error)
nil
end
# Find an aliases in the aliases map
# @param [Hash] aliases map of aliases
# @param [String] alias given alias
# @return [Yast::Term] belonging to the given alias or nil, if error
def WS_alias(aliases, alias_)
aliases = deep_copy(aliases)
found = Ops.get(aliases, alias_)
return WS_error(Builtins.sformat("Alias not found: %1", alias_)) if found.nil?
if Ops.is_list?(found)
return WS_error(Builtins.sformat("Invalid alias: %1", found)) if Ops.less_or_equal(Builtins.size(Convert.to_list(found)), 0)
found = Ops.get(Convert.to_list(found), 0)
end
return WS_error(Builtins.sformat("Invalid alias: %1", found)) if found.nil?
deep_copy(found)
end
# Decide if an alias is special
# @param [Hash] aliases map of aliases
# @param [String] alias given alias
# @return true if the given alias is special or nil, if not found
def WS_special(aliases, alias_)
aliases = deep_copy(aliases)
found = Ops.get(aliases, alias_)
if found.nil?
return Convert.to_boolean(
WS_error(Builtins.sformat("Alias not found: %1", alias_))
)
end
ret = false
ret = Ops.get_boolean(Convert.to_list(found), 1) if Ops.is_list?(found) && Ops.greater_than(Builtins.size(Convert.to_list(found)), 1)
ret
end
# Find a next item in the sequence
# @param [Hash] sequence sequence of dialogs
# @param [String] current current dialog
# @param [Symbol] ret returned value (determines the next dialog)
# @return next dialog (symbol), WS action (string) or nil, if error (current or next not found)
def WS_next(sequence, current, ret)
sequence = deep_copy(sequence)
found = Ops.get_map(sequence, current)
return WS_error(Builtins.sformat("Current not found: %1", current)) if found.nil?
# string|symbol next
next_ = Ops.get(found, ret)
return WS_error(Builtins.sformat("Symbol not found: %1", ret)) if next_.nil?
deep_copy(next_)
end
# Run a function from the aliases map
# @param [Hash] aliases map of aliases
# @param [String] id function to run
# @return returned value from function or nil, if function is nil or returned something else than symbol
def WS_run(aliases, id)
aliases = deep_copy(aliases)
Builtins.y2debug("Running: %1", id)
function = WS_alias(aliases, id)
return Convert.to_symbol(WS_error(Builtins.sformat("Bad id: %1", id))) if function.nil?
ret = Builtins.eval(function)
if !Ops.is_symbol?(ret)
return Convert.to_symbol(
WS_error(Builtins.sformat("Returned value not symbol: %1", ret))
)
end
Convert.to_symbol(ret)
end
# Push one item to the stack
# @param [Array] stack stack of previously run dialogs
# @param [Object] item item to be pushed
# @return the new stack or nil, if the stack is nil
def WS_push(stack, item)
stack = deep_copy(stack)
item = deep_copy(item)
return nil if stack.nil?
return Builtins.add(stack, item) if !Builtins.contains(stack, item)
found = false
newstack = Builtins.filter(stack) do |v|
next false if found
found = true if v == item
true
end
deep_copy(newstack)
end
# Pop one item from the stack (remove an item and return the stack top item)
# @param [Array] stack stack of previously run dialogsk
# @return [ new stack, poped value ] or nil if the stack is empty or nil
def WS_pop(stack)
stack = deep_copy(stack)
return nil if stack.nil?
num = Builtins.size(stack)
return nil if Ops.less_than(num, 2)
newstack = Builtins.remove(stack, Ops.subtract(num, 1))
poped = Ops.get(stack, Ops.subtract(num, 2))
[newstack, poped]
end
# The Wizard Sequencer
# @param [Hash] aliases the map of aliases
# @param [Hash] sequence the sequence of dialogs
# @return final symbol or nil, if error (see the y2log)
def Run(aliases, sequence)
aliases = deep_copy(aliases)
sequence = deep_copy(sequence)
# Check aliases and sequence correctness
return Convert.to_symbol(WS_error("CHECK FAILED")) if @docheck && WS_check(aliases, sequence) != true
stack = []
# string|symbol current
current = Ops.get(sequence, "ws_start")
return Convert.to_symbol(WS_error("Starting dialog not found")) if current.nil?
loop do
if Ops.is_symbol?(current)
Builtins.y2debug("Finished")
return Convert.to_symbol(current)
end
stack = WS_push(stack, current)
Builtins.y2debug("stack=%1", stack)
ret = WS_run(aliases, Convert.to_string(current))
if ret.nil? || !Ops.is_symbol?(ret)
return Convert.to_symbol(
WS_error(Builtins.sformat("Invalid ret: %1", ret))
)
elsif ret == :back
Builtins.y2debug("Back")
poped = []
special = true
loop do
return :back if Ops.less_than(Builtins.size(stack), 2)
poped = WS_pop(stack)
Builtins.y2debug("poped=%1", poped)
current = Ops.get(poped, 1)
stack = Ops.get_list(poped, 0)
special = WS_special(aliases, Convert.to_string(current))
Builtins.y2debug("special=%1", special)
break if !special
end
else
Builtins.y2debug("ret=%1", ret)
current = WS_next(
sequence,
Convert.to_string(current),
Convert.to_symbol(ret)
)
Builtins.y2debug("current=%1", current)
if current.nil?
return Convert.to_symbol(
WS_error(Builtins.sformat("Next not found: %1", current))
)
end
end
end
# Not reached
nil
end
publish variable: :docheck, type: "boolean", private: true
publish function: :WS_testall, type: "list (map)", private: true
publish function: :WS_check, type: "boolean (map, map)", private: true
publish function: :WS_error, type: "any (string)", private: true
publish function: :WS_alias, type: "any (map, string)", private: true
publish function: :WS_special, type: "boolean (map, string)", private: true
publish function: :WS_next, type: "any (map, string, symbol)", private: true
publish function: :WS_run, type: "symbol (map, string)", private: true
publish function: :WS_push, type: "list (list, any)", private: true
publish function: :WS_pop, type: "list (list)", private: true
publish function: :Run, type: "symbol (map, map)"
end
Sequencer = SequencerClass.new
Sequencer.main
end