openSUSE:YaST 快速教程
简介
YaST 模块使用 YCP 语言编写。要测试示例,请调用
/usr/lib/YaST2/bin/y2base example.ycp qt
用于图形用户界面,或用于 ncurses
/usr/lib/YaST2/bin/y2base example.ycp ncurses
你好,世界
// Hello, World in YCP
{
UI::OpenDialog(
`VBox(
`Label("Hello, World!"),
`PushButton("OK")
)
);
UI::UserInput();
UI::CloseDialog();
}
假设我们在 /etc 中有以下配置文件
# Demo config file for writing YaST2 modules: # # Server configuration for a (phoney) network game "sonic" # port = 5160 public = yes max_players = 40 map_dir = /usr/share/sonic/maps map = forest.map respawn_time = 10 ban_list = /etc/sonic/banned admin_password = "k1s43zz8"
// UI for simple sonic-server.conf editor
// using TextEntry for most fields
{
UI::OpenDialog(`VBox(
`TextEntry( "Port" ),
`CheckBox( "Public Access" ),
`TextEntry( "Max. Players" ),
`TextEntry( "Map Directory" ),
`TextEntry( "Map" ),
`TextEntry( "Respawn Time" ),
`TextEntry( "Ban List" ),
`TextEntry( "Admin Password" ),
`PushButton( "OK" )
)
);
UI::UserInput();
UI::CloseDialog();
}
但是,让我们使用特定的控件来处理我们应该允许的数据类型
// UI for simple sonic-server.conf editor
// using type-specific widgets
{
UI::OpenDialog(`VBox(
`Heading( "Sonic Server Configuration" ),
`IntField ("Port", 5155, 5164, 5160 ),
`CheckBox ( "Public Access" ),
`IntField ( "Max. Players", 2, 999, 10 ),
`TextEntry( "Map Directory" ),
`TextEntry( "Map" ),
`IntField ( "Respawn Time", 0, 3600, 5 ),
`TextEntry( "Ban List" ),
`Password ( "Admin Password" ),
`PushButton( "OK" )
)
);
UI::UserInput();
UI::CloseDialog();
}
为了从控件(用户界面元素,例如复选框、文本输入字段等)中获取值,我们需要为它们命名,所以让我们为每个控件添加 ID
// UI for simple sonic-server.conf editor
// using IDs for all widgets so values can be fetched and stored
{
UI::OpenDialog(`VBox(
`Heading( "Sonic Server Configuration" ),
`IntField (`id(`port ), "Port", 5155, 5164, 5160 ),
`CheckBox (`id(`public ), "Public Access" ),
`IntField (`id(`max_pl ), "Max. Players", 2, 999, 10 ),
`TextEntry(`id(`map_dir ), "Map Directory" ),
`TextEntry(`id(`map ), "Map" ),
`IntField (`id(`respawn ), "Respawn Time", 0, 3600, 5 ),
`TextEntry(`id(`ban_list ), "Ban List" ),
`Password (`id(`admin_pw ), "Admin Password" ),
`PushButton(`id(`ok), "OK" )
)
);
UI::UserInput();
UI::CloseDialog();
}
让我们进一步完善它,添加边距和一个取消按钮
// UI for simple sonic-server.conf editor
//
// look somewhat polished: margins, spacing, alignment
// added a "Cancel" button
{
UI::OpenDialog( `MarginBox( 2, 0.3,
`VBox(
`Heading( "Sonic Server Configuration" ),
`VSpacing( 1 ),
`IntField (`id(`port ), "Port", 5155, 5164, 5160 ),
`Left(
`CheckBox (`id(`public ), "Public Access" )
),
`IntField (`id(`max_pl ), "Max. Players", 2, 999, 10 ),
`TextEntry(`id(`map_dir ), "Map Directory" ),
`TextEntry(`id(`map ), "Map" ),
`IntField (`id(`respawn ), "Respawn Time", 0, 3600, 5 ),
`TextEntry(`id(`ban_list ), "Ban List" ),
`Password (`id(`admin_pw ), "Admin Password" ),
`VSpacing( 0.5 ),
`HBox(
`PushButton(`id(`ok ), "OK" ),
`PushButton(`id(`cancel ), "Cancel" )
)
) ) );
UI::UserInput();
UI::CloseDialog();
}
我们可以将其模块化。这意味着,我们将它逐部分构建并全部保存到一个术语中。然后,在打开对话框时,我们将该术语传递给对话框,而不是所有对话框控件。这可以使代码更易于阅读,并且允许我们在多个对话框中使用相同的组件。
// UI for simple sonic-server.conf editor
// dialog modularized
{
term fields =
`VBox(
`IntField (`id(`port ), "Port", 5155, 5164, 5160 ),
`Left(
`CheckBox (`id(`public ), "Public Access" )
),
`IntField (`id(`max_pl ), "Max. Players", 2, 999, 10 ),
`TextEntry(`id(`map_dir ), "Map Directory" ),
`TextEntry(`id(`map ), "Map" ),
`IntField (`id(`respawn ), "Respawn Time", 0, 3600, 5 ),
`TextEntry(`id(`ban_list ), "Ban List" ),
`Password (`id(`admin_pw ), "Admin Password" )
);
term button_box =
`HBox(
`PushButton(`id(`ok ), "OK" ),
`PushButton(`id(`cancel ), "Cancel" )
);
UI::OpenDialog(
`MarginBox( 2, 0.3,
`VBox(
`Heading( "Sonic Server Configuration" ),
`VSpacing( 0.7 ),
fields,
`VSpacing( 0.5 ),
button_box
)
)
);
UI::UserInput();
UI::CloseDialog();
}
现在,我们将它变成一个向导
// UI for sonic-server.conf editor in "wizard" look & feel
{
import "Wizard";
term fields =
`VBox(
`IntField (`id(`port ), "Port", 5155, 5164, 5160 ),
`Left(
`CheckBox (`id(`public ), "Public Access" )
),
`IntField (`id(`max_pl ), "Max. Players", 2, 999, 10 ),
`TextEntry(`id(`map_dir ), "Map Directory" ),
`TextEntry(`id(`map ), "Map" ),
`IntField (`id(`respawn ), "Respawn Time", 0, 3600, 5 ),
`TextEntry(`id(`ban_list ), "Ban List" ),
`Password (`id(`admin_pw ), "Admin Password" )
);
string help_text = "Help text (to do)";
Wizard::OpenAcceptDialog();
Wizard::SetContents( "Sonic Server Configuration", // Dialog title
`MarginBox( 10, 0, fields ), // Dialog contents
help_text,
true, // "Cancel" button enabled?
true ); // "Accept" button enabled?
UI::UserInput();
UI::CloseDialog();
}
现在我们想从配置文件中读取和写入数据。为此,我们编写一个基于配置文件代理的代理。
/**
* File: etc_sonic_sonic_server_conf.scr
* Summary: Agent for reading/writing (bogus) sonic server configuration
* Author: Stefan Hundhammer <sh@suse.de>
* Access: read / write
*
* Reads/writes the values defined in /etc/sonic/sonic-server.conf
*/
.etc.sonic.sonic_server_conf
`ag_ini(
`SysConfigFile("/etc/sonic/sonic-server.conf")
)
这是使用代理来读取和写入的向导的一个工作版本
// sonic-server.conf editor that actually reads and writes the config file
//
// trivial version - lots of copy & paste
{
import "Wizard";
// Read values from file /etc/sonic/sonic_server.conf
string port = (string) SCR::Read( .etc.sonic.sonic_server_conf.port );
string public = (string) SCR::Read( .etc.sonic.sonic_server_conf.public );
string max_players = (string) SCR::Read( .etc.sonic.sonic_server_conf.max_players );
string map_dir = (string) SCR::Read( .etc.sonic.sonic_server_conf.map_dir );
string current_map = (string) SCR::Read( .etc.sonic.sonic_server_conf.map );
string respawn = (string) SCR::Read( .etc.sonic.sonic_server_conf.respawn_time );
string ban_list = (string) SCR::Read( .etc.sonic.sonic_server_conf.ban_list );
string admin_pw = (string) SCR::Read( .etc.sonic.sonic_server_conf.admin_password );
// Build dialog
term fields =
`VBox(
`IntField (`id(`port ), "Port", 5155, 5164, tointeger( port ) ),
`Left(
`CheckBox (`id(`public ), "Public Access", public == "yes" )
),
`IntField (`id(`max_pl ), "Max. Players", 2, 999, tointeger( max_players ) ),
`TextEntry(`id(`map_dir ), "Map Directory" , map_dir ),
`TextEntry(`id(`map ), "Map" , current_map ),
`IntField (`id(`respawn ), "Respawn Time", 0, 3600, tointeger( respawn ) ),
`TextEntry(`id(`ban_list ), "Ban List" , ban_list ),
`Password (`id(`admin_pw ), "Admin Password" , admin_pw )
);
string help_text = "Help text (to do)";
Wizard::OpenAcceptDialog();
Wizard::SetContents( "Sonic Server Configuration", // Dialog title
`MarginBox( 10, 0, fields ), // Dialog contents
help_text,
true, // "Cancel" button enabled?
true ); // "Accept" button enabled?
// Dialog input loop
symbol button = nil;
repeat
{
button = (symbol) UI::UserInput();
} until ( button == `accept ||
button == `cancel );
if ( button == `accept )
{
// Read field contents
port = sformat( "%1", UI::QueryWidget(`port, `Value ) );
public = (boolean) UI::QueryWidget(`public, `Value ) ? "yes" : "no";
max_players = sformat( "%1", UI::QueryWidget(`max_pl, `Value ) );
map_dir = (string) UI::QueryWidget(`map_dir, `Value );
current_map = (string) UI::QueryWidget(`map, `Value );
respawn = sformat( "%1", UI::QueryWidget(`respawn, `Value ) );
ban_list = (string) UI::QueryWidget(`ban_list, `Value );
admin_pw = (string) UI::QueryWidget(`admin_pw, `Value );
// Write values back to file /etc/sonic/sonic_server.conf
SCR::Write( .etc.sonic.sonic_server_conf.port , port );
SCR::Write( .etc.sonic.sonic_server_conf.public , public );
SCR::Write( .etc.sonic.sonic_server_conf.max_players , max_players );
SCR::Write( .etc.sonic.sonic_server_conf.map_dir , map_dir );
SCR::Write( .etc.sonic.sonic_server_conf.map , current_map );
SCR::Write( .etc.sonic.sonic_server_conf.respawn_time , respawn );
SCR::Write( .etc.sonic.sonic_server_conf.ban_list , ban_list );
SCR::Write( .etc.sonic.sonic_server_conf.admin_password , admin_pw );
// Post a popup dialog
UI::OpenDialog(`VBox(
`Label( "Values written to\n/etc/sonic/sonic_server.conf" ),
`PushButton(`opt(`default), "&OK" )
)
);
UI::TimeoutUserInput( 4000 ); // millisec
UI::CloseDialog(); // Closes the popup
}
// The (main) dialog needs to remain open until here,
// otherwise the widgets that are queried no longer exist!
UI::CloseDialog();
}
但是现在我们想进行一些重构,将代码拆分成函数
// sonic-server.conf editor that actually reads and writes the config file
//
// more elegant version
//
// demo showing how to work with functions, lists, maps, terms, paths
{
import "Wizard";
typedef map<string, any> ConfigEntry;
list<ConfigEntry> sonic_config =
[
$[ "name": "port" , "type": "int" , "def": 5455, "min": 5155, "max": 5164 ],
$[ "name": "public" , "type": "bool" , "def": true ],
$[ "name": "max_players" , "type": "int" , "def": 50, "min": 2, "max": 999 ],
$[ "name": "map_dir" , "type": "string" ],
$[ "name": "map" , "type": "string" ],
$[ "name": "respawn_time" , "type": "int" , "def": 7, "min": 0, "max": 3600 ],
$[ "name": "ban_list" , "type": "string" ],
$[ "name": "admin_password", "type": "password" ]
];
path sonic_config_path = .etc.sonic.sonic_server_conf;
// Mapping from config entry names to widget captions
// (in a real life example this would go to the ConfigEntry map, too)
map<string, string> widget_caption =
$[
"port" : "Port",
"public" : "Public Access",
"max_players" : "Max. Players",
"map_dir" : "Map Directory",
"map" : "Map",
"respawn_time" : "Respawn Time",
"ban_list" : "Ban List",
"admin_password" : "Admin Password"
];
/**
* Create a widget for a config entry
*
* Parameters:
* entry map describing the config entry
* scr_base_path SCR path to use (entry["name"] will be added)
*
* Return:
* term describing the widget
**/
term entryWidget( ConfigEntry entry, path scr_base_path )
{
string name = (string) entry[ "name" ]:nil;
string type = (string) entry[ "type" ]:"string";
string caption = widget_caption[ name ]:name;
// Read current value from config file
any value = SCR::Read( add( scr_base_path, name ) );
// Create corresponding widget
term widget = nil;
if ( type == "string" )
{
widget = `TextEntry( `id( name ), caption, value != nil ? value : "" );
}
else if ( type == "password" )
{
widget = `Password( `id( name ), caption, value != nil ? value : "" );
}
else if ( type == "bool" )
{
widget = `Left( `CheckBox( `id( name ), caption, value == "yes" ) );
}
else if ( type == "int" )
{
integer min = tointeger( entry[ "min" ]:0 );
integer max = tointeger( entry[ "max" ]:65535 );
widget = `IntField( `id( name ), caption, min, max,
value != nil ? tointeger( value ) : min );
}
else
{
y2error( "Unknown type in config entry: %1", entry );
}
return widget;
}
/**
* Create widgets in a VBox for a list of config entries
*
* Parameters:
* scr_base_path SCR path to use (entry["name"] will be added)
* entries list of maps describing the config entries
*
* Return:
* widget term
**/
term configWidgets( list<ConfigEntry> entry_list, path scr_base_path )
{
term vbox = `VBox();
foreach( ConfigEntry entry, entry_list,
{
term widget = entryWidget( entry, scr_base_path );
if ( widget != nil )
vbox = add( vbox, widget );
});
return vbox;
}
/**
* Write a list of configuration entries to file.
* Get the current value for each entry from a widget in the current dialog.
**/
void writeConfig( list<ConfigEntry> entry_list, path scr_base_path )
{
foreach( ConfigEntry entry, entry_list,
{
string name = (string) entry[ "name" ]:nil;
if ( name == nil )
{
y2error( "Entry without name in entry list: %1", entry );
}
else
{
any value = UI::QueryWidget(`id( name ), `Value );
if ( is( value, boolean ) )
value = ( (boolean) value ) ? "yes" : "no";
SCR::Write( add( scr_base_path, name ), value );
}
});
}
/**
* Display an information popup dialog with a timeout.
* A timeout of 0 means "no timeout, wait for user to click".
**/
void infoPopup( string message, integer timeout_sec )
{
UI::OpenDialog(`VBox(
`Label( message ),
`PushButton(`opt(`default), "&OK" )
)
);
UI::TimeoutUserInput( timeout_sec * 1000 );
UI::CloseDialog();
}
//
// Main
//
string help_text = "Help text (to do)";
term fields = configWidgets( sonic_config, sonic_config_path );
Wizard::OpenAcceptDialog();
Wizard::SetContents( "Sonic Server Configuration", // Dialog title
`MarginBox( 10, 0, fields ), // Dialog contents
help_text,
true, // "Cancel" button enabled?
true ); // "Accept" button enabled?
// Dialog input loop
symbol button = nil;
repeat
{
button = (symbol) UI::UserInput();
} until ( button == `accept ||
button == `cancel );
if ( button == `accept )
{
writeConfig( sonic_config, sonic_config_path );
infoPopup( "Values written to\n/etc/sonic/sonic_server.conf", 4 );
}
// The (main) dialog needs to remain open until here,
// otherwise the widgets that are queried no longer exist!
UI::CloseDialog();
}