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();
}