The wikis are now using the new authentication system.
If you did not migrate your account yet, visit https://idp-portal-info.suse.com/

YaST/教程/模块快速教程

< YaST‎ | 教程(Redirected from YaST/Tutorials/QuickModule)
Jump to: navigation, search

Susemini.png 本文处于需要翻译的文章的状态,欢迎您积极参与翻译与修订。 翻译人员:无,修订人员:无。

介绍

YaST 模块采用YCP语言编写。可以这样来测试样例:

/usr/lib/YaST2/bin/y2base example.ycp qt

如果使用图形化用户接口或是使用Ncurses库,是这样:

/usr/lib/YaST2/bin/y2base example.ycp ncurses

Hello World

YCP的Hello World程序

// Hello, World in YCP
{
    UI::OpenDialog(
         `VBox(
         `Label("Hello, World!"),
         `PushButton("OK")
         )
         ); //打开对话框,注意括号的嵌套
    UI::UserInput();  //用户输入消息循环
    UI::CloseDialog();//关闭对话框
}
Pushbutton.png

Imagine we have the following config file in /etc:

假定在/etc目录下有一个配置文件,内容是:

# Demo config file for writing YaST2 modules:
# 编写YaST2模块的样例配置文件
# 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();
}
Window.png

But let's use specific widgets for the kind of data we are supposed to allow there:

亦可采用特定窗体来生成我们指定数据类型:

// 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();
}
Widget1.png

In order to fetch the values from the widgets (user interface elements, such as checkboxes, text entry fields, etc.), we need to name them, so let's add IDs to each one:

为取回窗体(象检查框、文本框等用户接口控件)里的数据,需要对接口控件命名,给它们都加一个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();
}

Let's polish it a little more, adding margins and a cancel button:

再优化一下,加个边框和取消按钮:

// 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();
}
Widget3.png

We can modularize it. That means, we construct it part by part and save them all in a term. Then, when opening the dialog, we pass the term instead of all the dialog widgets. That can be useful to make the code easier to read, and it allows us to use the same component in more than one dialog.

下面做模块化工作,也就是在一个序列中一块一块创建并保存它们。 那么当打开对话框时,传入一个序列而不是对话框里的窗体控件。这样可以使得代码可读性好,并且可让相似构件在多个对话框里使用。

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

Now, we turn it into a wizard:

现在,放到一个向导窗体里:

// 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();
}
Wizard.png

Now we want to read and write data to and from the config file. For that, we write an agent, based on the config file agent.

现在可以从一个配置文件里读取和写入数据了。为此,我们写一个代理,基于配置文件代理。

/**
 * 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")
)

Here's a working version of the wizard which uses the agent to read and write:

这是向导窗体的工作版本,它使用代理进行读写:

// 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();
}
Wizard2.png

But now we want to do some refactoring, splitting the code into functions:

现在想做点修正,把代码切分到几个函数里:

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