1.3. Check YCP Syntax

1.3.1. Quick Start

Simply invoke check_ycp with the YCP file(s) you wish to check as arguments:

check_ycp myfile.ycp
      

check_ycp *.ycp
      

The error messages should be self explanatory. They stick to the GNU standards for error messages, so you can use your favourite editor's (e.g. Emacs) function to process them.

Most of the checks can individually be turned off. Type

check_ycp -h
      

for a complete list of command line options. Those options are intentionally not listed here since such a list would inevitably be outdated before too long.

1.3.2. Why this Document?

Even though using check_ycp is pretty straightforward, some background information is useful in order to understand what it does, its output and the limitations of this tool - in short, what you can expect it to do and why not to blindly rely on it.

check_ycp is far from fool proof. In fact, it is pretty dumb. It just tries to parse YCP code ("try" is the operative word here!) and applies various checks for obvious programming errors. Some errors it will catch and report, many it will not. But we (i.e. the YaST2 core development team) decided we'd rather have a tool with limited capabilities than none at all.

Another reason for writing this document is pointing out why we try to enforce certain things, many of which are because of ergonomics or mere conventions in developing as a team, not true requirements of YaST2 or the YCP language.

1.3.3. Header Comment Checks

The YaST2 team uses a standardized file header format for YCP modules. Standard fields are included there for various purposes - see the "why" sections of the individual checks.

Everything up to the first opening brace "{" outside a comment is considered part of the header. Nothing outside this portion of the file is checked. You may, however, open and close as many comments as you like up to this opening brace.

Leading asterisks "*" at the start of lines are silently discarded since they are often used to beautify multi line comments.

The comment markers themselves of course are also discarded for the checks:

  • /*
  • */
  • //

1.3.4. Filename Check

What

If present, the contents of a Module field is checked against the current file name.

Why

Much code gets written by copying existing code. There is nothing wrong with that (in fact, it saves a lot of work), but when you do, please change fields accordingly - fields like Author, Maintainer, Purpose etc. - and the file name in Module.

How

This file name is particularly easy to check (plus, it's the only one that can reliably be checked), so check_ycp checks it: It compares the base name (not the complete path) of the current file to what you specified in Module: in the header.

1.3.5. Author / Maintainer Entry Check

What

check_ycp checks the file header for presence of at least one of

  • Author:
  • Authors:
  • Maintainer:
  • Maintainers:

If found, each entry is checked for some contents, i.e. it may not be completely empty (but use whitespace as you like).

The contents must include something that looks like an e-mail address.

Why

There must be at least one person to contact when there are any problems or questions about the module. The full name is desired, but at least an e-mail address must be there to get in contact with the maintainer or the author.

How

The fields are checked for presence of something like somebody@somewhere.domain - in fact only for something before the at sign "@" and something with a period "." behind it.

1.3.6. CVS Id: Marker Check

What

Presence of a Id CVS / RCS identity marker is checked, e.g.

            Id myfile.ycp,v 1.2 2001/02/14 18:04:50 sh Exp 
	

Why

This CVS / RCS ID is the only way of finding out exactly what CVS revision the file has and what change date. The file date (what ls -l shows) is absolutely unreliable and irrelevant: This may have changed just by copying the file around which didn't change anything.

This is important for bug tracking and for finding and fixing bugs - only when a developer knows what version of a file has been used he has a chance to reproduce a bug - or even make sure that a supposedly fixed bug didn't turn up again.

How

Presence of

Id:
	

is checked. There may be more characters before the closing dollar sign "$", but the exact contents is not checked.

[Note]Note

When creating a new file, it is absolutely sufficient to include the unexpanded string ("Id:") somewhere in the file. CVS or RCS will automatically expand this to the full ID string.

1.3.7. Translatable Messages Checks

1.3.7.1. textdomain Check

What

If there is any message that is marked for translation with _("..."), there must be a textdomain statement.

Why

The YaST2 translator module needs to know where to take the messages to be translated from. This is what the textdomain specification does.

Technically one textdomain statement somewhere in the YCP program would be sufficient, i.e. include files or modules called with CallFunction() don't really require an additional textdomain specification.

However, it is highy recommended all YCP files with translatable messages include their own textdomain statement so each YCP file is self-sufficient in that regard, thus more easily reusable for other purposes. This policy is enforced with this check.

How

After being stripped of all comments, the entire YCP code is scanned for the translation marker sequence: An underscore immediately followed by an opening parenthesis: _(

If this sequence is found, presence of translatable messages is assumed. If no textdomain statment is found there will be an error.

On the other hand, if there is no text to translate, a textdomain statement is not necessary (but it can't hurt).

[Note]Note

Theoretically the "_(" sequence contained in a literal string (i.e. within double quotes "...") could falsely trigger this error, too. But if you do that, you are very likely to run into trouble with other tools as well - most likely even the original getext tools regularly used to extract the messages for translation. Bottom line: Don't do that.

1.3.8. RichText / HTML Sanity Check

Literal strings in YCP code that contains HTML tags are usually help text that will be displayed in the YaST2 RichText widget. This HTML text is subjected to the sanity checks explained below.

Please notice that everything within double quotes " will be checked that contains anything surrounded by angle brackets <...> - i.e. anything that looks remotely like an HTML tag. Unknown tags will be silently ignored, but the entire text within the quotes will be checked.

Limitation:  If a portion of help text lacks any HTML tag, it will not be checked since it will not be recognized by check_ycp as help text. Such completely wrong portions of help text will slip through undetected, thus unchecked.

1.3.8.1. Completeness of <p> / </p> Paragraph Tags

What

Each HTML text must start with a <p> tag and end with a </p> tag.

There must be a corresponding closing </p> tag for each opening <p> tag.

Why

This is a basic requirement of HTML. The underlying YaST2 widgets may or may not be forgiving enough to tolerate missing tags, but we'd rather not rely on that.

Besides, no other types of paragraphs other than plain text paragraphs <p> ... </p> are desired in YaST2 help texts - in particular, no large font boldface headings etc.

How

See the intro of this section.

1.3.8.2. Text Before, After, Between Paragraphs

What

For each portion of HTML text:

  • No text before the first <p> tag is permitted.
  • No text after the last </p> tag is permitted.
  • No text between a closing </p> and the next opening <p> tag is permitted.

Why

Each of those cases is a simple yet common HTML syntax error.

How

See the intro of this section.

1.3.8.3. No More Than One Paragraph per Message

What

Each single portion of HTML text may contain exactly one paragraph, i.e. one <p> ... </p> pair.

Why

This is a convention to make life easier for the translators.

The tools used for extracting translatable texts from the sources (GNU gettext) detect differences between the last translated version of a message and the current message from the latest source. They mark such messages as fuzzy, i.e. the (human) translator is asked to have a good look at it and decide whether there has been a real change in the message (thus it needs to be retranslated) or just a cosmetic change (fixed typo, inserted whitespace, reformatted the paragraph etc.).

This is a tedious task and it gets more tedious the longer each individual portion of text becomes. Changes from the old to the new version are hard to find if the portions are very long.

Plus, if they are that long it is very likely that always somewhere something has changed, thus the entire text is marked as fuzzy and needs careful manual checking which is not really necessary for all the text.

Workaround

Split your help texts and use the YCP string addition operator to put them together.

Don't: 

help_text = _("<p>
bla blurb bla ...
blurb bla blurb ...
bla blurb bla ...
</p>
<p>
bla blurb bla ...
blurb bla blurb ...
bla blurb bla ...
</p>");
	  

Instead, do: 

// Help text (HTML like)
help_text = _("<p>
bla blurb bla ...
blurb bla blurb ...
bla blurb bla ...
</p>");

// Help text (HTML like), continued
help_text = help_text + _("<p>
bla blurb bla ...
blurb bla blurb ...
bla blurb bla ...
</p>");
	  

Please also notice the comments for the translators just above the text. The gettext tools will automatically extract them along with the text to translate and put them into the .po file. The translators can use them as additional hints what this text is all about.

How

See the intro of this section.

1.3.8.4. Excess Forced Line Breaks <br> after Paragraphs

What

Forced line break tags <br> are discouraged, especially after a paragraph end tag </p>.

Why

Such forced line breaks are plain superfluous. The HTML renderer will format the paragraph automatically - after each paragraph there will be a newline and some empty space to set each paragraph apart from the next.

There is no need nor is it desired to add extra empty space between paragraphs. This just looks plain ugly, even more so if this results in different spacings between several paragraphs of the same help text.

The most superfluous of those excess line breaks are those at the very end of a help text - after the last paragraph. Not only are they not good for anything, they sometimes even cause a vertical scroll bar to be displayed even though this would not be necessary otherwise.

Plus, there have been cases where erstwhile last help text paragraphs had been rearranged so they now are in the middle of the help text - but unfortunately the trailing <br> tag had been forgotten and moved along with the paragraph, thus causing different inter-paragraph spacings.

To make things even worse, fixing this breaks the translation for the affected paragraph: It will be marked as fuzzy just because of this even though it has not really changed.

We cannot entirely get rid of the <br> tags (but we would like to). Sometimes they are needed within paragraphs. But at least those at the end of paragraphs we can do without.

How

<br> after </p> (maybe with anything in between) is rejected. All other <br> tags are silently ignored.

1.3.9. Widget / UI Function Parameter Checks

Parameters to YaST2 UI widgets plus some commonly used functions (e.g. Wizard::SetContents(), Popup::Message() etc.) are checked where possible - if the parameters are simple string constants, maybe surrounded by translation markers ("_("...")").

Optional widget parameters like `opt(...) or `id(...) are ignored.

The following examples will be checked:

PushButton("OK");
      

PushButton( _("Cancel"));
      

PushButton(`id(`apply), _("Apply"));
      

PushButton(`opt(`default), _("OK"));
      

More complex parameters like variable contents or YCP terms cannot be checked.

The parser used in check_ycp for that is really dumb. In fact, it only scans for keywords like PushButton outside string constants, tries to find the corresponding matching pair of parentheses "(...)" and splits everything inside into comma-separated subexpressions.

Only the most basic of those subexpressions are checked - only simple string constants "..." or string constants marked for translation _("...").

The following examples will not be checked:

CheckBox( "/dev/"+device );

CheckBox( sformat("/dev/%1"), device );

CheckBox( GetDevName() );

	string message = "OK";
	PushButton( message );
    

1.3.9.1. Keyboard Shortcut Check

What

Widgets that can have a keyboard shortcut (one character marked with an ampersand "&") are checked for presence of a keyboard shortcut.

[Note]Note

Consistency of the keyboard shortcuts is not checked, only presence. check_ycp cannot know which widgets will be on-screen at the same time, thus it cannot find out whether the same keyboard shortcut has been assigned twice to different widgets.

Why

This is for users whose mouse doesn't work (especially during installation time) as well as for experienced users who prefer working with the keyboard. Navigation from one widget to another is much easier when each widget that can get the keyboard focus can be reached with an [Alt] key sequence rather than repeatedly using the [Tab] key and/or the cursor keys.

There may be a lot more widgets that can have keyboard shortcuts than you expected. Basically, every widget that can somehow be operated with the keyboard (even if it is only scrolling) and that has an associated label (within, above or beside) can have a keyboard shortcut and should get one.

How

The widget parameter that acts as a label is checked for presence of exactly one ampersand "&".

See the widget checks of this section for more.

1.3.9.2. Translatable Messages Check

What

Widget parameters that are displayed literally as text are checked for translation markers ("_("...")").

Why

Every text message that ever gets to the end user is to be translated into the user's native language. This can only be made sure if the message is marked for translation.

How

See the intro of this section.

1.3.10. Standardized Lib Function Checks

1.3.10.1. Duplicate Definitions of Wizard Lib Functions

What

Presence of definitions of functions from the wizard lib (Package yast2) outside the wizard library itself is checked such as Wizard::SetContents() etc.

Why

At the start of YaST2 develompent there was no other way of sharing code other than simply copying it. Those days are gone; YCP now supports an include mechanism similar to C or C++.

Very general code like how to create the typical YaST2 wizard window layout has now been moved to the wizard lib, a collection of include files that provide such facilities. We want to get rid of duplicate code as soon as possible for obvious reasons (consistency, maintainability, efficiency).

How

If "define" followed by one of the known function names of the wizard lib is found outside the file where this is supposed to be, a warning is issued. Both function names and file names are hardwired within check_ycp.

1.3.10.2. Definitions and Usage of Obsolete Functions

What

Usage or presence of definitions of known obsolete functions is checked, e.g. Popup::Message(), Popup::YesOrNo() etc.; using an equivalent replacement function from the wizard lib's common_popups.ycp include file is suggested.

Why

Those functions are now superseded by those from common_popups.ycp. The replacement functions usually require less parameters (thus are easier to use) and use a common and consistent widget layout.

How

The definitions are checked very much like the wizard function definitions above; function and file names are hardwired here as well.

Usage of the obsolete functions is checked simply by checking for occurrence of the function name followed by an opening parenthesis (maybe with whitespace in between) somewhere in the code.

1.3.10.3. Usage of Predefined Messages

What

Presence of predefined message strings is checked, e.g. "&Next", "&Back" etc.; using a corresponding function from the wizard lib (Label Module) is suggested, e.g. Label::NextButton(), Label::BackButton() etc.

Why

  • Ease the burden on the translators - with the predefined messages they don't need to translate the same standard texts over and over again.

  • Consistent messages for the same type of buttons etc. throughout all of YaST2.

  • Consistent keyboard shortcuts for the same button throughout all of YaST2.

  • If we ever need to change one of those standard messages, we can do that centralized.

How

The YCP code, stripped of comments, is checked for any one of the predefined messages (including any keyboard shortcuts that may be there), surrounded by translation markers ("_("...")").

Limitations

Differences in spelling or only in whitespace will not be caught. If there is no or another keyboard shortcut, the message will not be considered the same - so if anybody uses "Ne&xt" rather than "&Next", this will go undetected.

1.3.11. Alternative Variable Declarations

What

Alternative variable declarations are rejected, e.g.

string|void value = anything();
symbol|term result = UI::UserInput();
integer|string x = 42;

	  

Why

Just about the only situation where this made sense was when a variable might sometimes be nil to indicate some error condition. All other variants of this are of purely academic nature or (more likely) poor programming style. Since all YCP types can be nil now, however, this feature becomes totally redundant. It will very likely be dropped in the near future.

How

The entire YCP code, stripped of comments, is checked for occurences of one of the primitive YCP types (string, integer, boolean, map, list, any, void etc.) followed by a pipe sign | (maybe with whitespace before or after it) and another primitive YCP type.

1.3.12. Checking YCP Examples

You probably don't want to perform all of the available checks for simple YCP examples. Those should be concise and written for legibility rather than for completeness. They will usually not contain a standard format file header with all bells and whistles, no translation markers etc. - you don't want to bloat HelloWorld.ycp with all that stuff.

check_ycp has a special example mode for just this purpose: It turns off all checks that don't make sense for simple examples, yet allows you to use check_ycp anyway. If you think "well, what's left then?" think about the future. check_ycp can and will be expanded to cover more and more checks, and even your examples can benefit from it.

For simple YCP examples (and only for them, please!) invoke check_ycp with the -x command line option:

check_ycp -x HelloWorld.ycp
	  

This turns off all checks that don't make sense for examples.

1.3.13. check_ycp and Emacs

check_ycp and Emacs go together well:

  • Load a YCP file into Emacs.
  • Invoke the Emacs compile command:

    M-x compile
    	    
  • Edit the compile command ("make -k" by default) in the minibuffer; change it to the check_ycp command you wish to invoke (you only need to do this once for each Emacs session):

    check_ycp *.ycp
    		

  • Hit Return
  • Use the next-error function to go to the next error check_ycp has reported. The corresponding YCP file will automatically be loaded into Emacs if needed, and Emacs will jump to the corresponding line within that file.

If you haven't done so already, you might want to bind the compile and next-error functions to keys in your ~/.emacs file, e.g.

(global-set-key "f42" 'compile)
(global-set-key "f43" 'next-error)
      

The real challenge here is to find a key that is not already in use for some other important function.

If you are a real hardcore YCP hacker, you can even go so far and change the default compile command to check_ycp in ~/.emacs:

(setq compile-command "check_ycp *.ycp")
      

1.3.14. Extending check_ycp

1.3.14.1. Adding new Widgets / UI Functions

Everybody should be able to add checks for a new widget or a new function that uses keyboard shortcuts (unlikely) or translatable messages (very likely) - even without any knowledge of Perl:

  1. Locate the check_widget_params() function.

  2. Add your widget to the list (the large regexp) near the function beginning, where all the other widgets are. Be careful not to include any whitespace (blanks or tabs) inside the parentheses. Wrong:

    ( MyNewWidget ) |

    OK:

    (MyNewWidget) |

  3. Add an elsif() branch to the large if()...elsif()...elsif() construction:

    elsif ( $widget =~ /MyWidget/ )
    {
        check_keyboard_shortcut ( $widget, $line_no, 1, @args );
        check_translation       ( $widget, $line_no, 1, @args );
    }
    	      

You might have to change the third parameter according to your widget or function: This is the number of the parameter to be checked (the first one is 1) after all `opt() and `id() parameters have been removed.

Of course you can omit the keyboard shortcut check (check_keyboard_shortcut()) if it doesn't make sense for your widget or function.

If there is more than one parameter to be checked for translatable messages, add a call to check_translation() for each.

1.3.14.2. Other Extensions

Like Linus Torvalds once said: "Use the source, Luke!" ;-)

check_ycp's sources are extensively commented, even the many regular expressions used there. But changing those regexps really requires some in-depth knowledge of regexps in general and Perl regexps in particular. If you feel unsafe, better not touch them.

Other than that, use your creativity.