Welcome to TemplateApp Docs

TemplateApp is a closed source Python package that is used to help individuals to generate TextFSM template by using plain text.  TemplateApp offers two licenses: and

Non-Commercial-Use License
Development Status ALPHA
Availability Online App by www.geekstrident.com
Installation N/A
Cost Free of Charge
Ad-Free With Ad
Test Solution N/A
Tech Support N/A
Integration N/A
Commercial-Use License
Development Status ALPHA
Availability Online or Offline App by CLIENT DEPLOYMENT or
Online App by PARTNER / THIRD-PARTY services
Installation Github or Offline-package
Cost Cost per Strategy
Ad-Free YES
Test Solution YES
Tech Support YES
Integration YES
(FYI: Helpful Videos )

Mitigating Human Error


Assuming user1 needs to create TextFSM template to parse an output of "ls -l" command line
-rw-r--r--  1 user1  staff  433 Feb 26 15:08 Problem Statement drwxr-xr-x 14 user1  staff  128 Jul  5 13:23 test -rw-r--r--  1 user1  staff  748 Jul  5 12:13 test_ws.c                        

Assuming user1 wants to use WebTemplate application to solve this problem.  First, user1 edits the output by adding a group of hyphen line as below

---------- -- ------ ------ --- ------------ ------------------------------- -rw-r--r--  1 user1  staff  433 Feb 26 15:08 Problem Statement drwxr-xr-x 14 user1  staff  128 Jul  5 13:23 test -rw-r--r--  1 user1  staff  748 Jul  5 12:13 test_ws.c                        

Next, user1 uses WebTemplate - Builder to build a first draft

Builder - Example 1: The First Draft User1 is unsure how to name columns and needs suggestion to properly parse data.  User1 asks reviewer or data owner for helps.  The ANSWER MIGHT BE
1. column #5 needs exact three mixed word. 2. column #6 can be one or more mixed words. 3. Rename column 0-6 to the following:     type_n_perms, hard_links, fowner,     fgroup, fsize, date_n_time, file_name                                
Builder - Example 1: The Second Draft After completed the second draft, user1 sends result to reviewer for reviewing.  The ANSWER COULD BE
type_n_perms column should break into two columns: ftype and fperms.    + The ftype column can be letter or hyphen.  + The fperms column can be as same as type_n_perms                                        
Builder - Example 1: The Third Draft Furthermore, reviewer REQUESTS user1 to make a modification to match this particular output
-rw-r--r--  1 user1  staff  433 Feb 26 15:08 Problem Statement drwxr-xr-x 14 user1  staff  128 Jul  5 13:23 test lrw-r--r--  1 user1  staff  748 Jul  5  2022 test_ws.c -> test_whitespace.c                                        

And, the fourth draft can be

Builder - Example 1: The Third Draft

Assuming the fourth draft is the final solution.  Therefore, communication and resolving problem have performed by using plain text language which might reduce confusion or improve clarification because technical work has simplified to human readable problem.

FIRST DRAFT

# Non-commercial use: generated by WebTemplate v.0.1.11 (geekstrident.com) # Created Date: 2023-08-29 14:42 ################################ start() mixed_word(var_col0)  digits(var_col1)  word(var_col2)  letters(var_col3)  digits(var_col4)  mixed_word(var_col5, at_most_2_group_occurrences)  mixed_word(var_col6, at_most_1_group_occurrences) end() -> record                        

FINAL SOLUTION (i.e., 4th DRAFT)

# Non-commercial use: generated by WebTemplate v.0.1.11 (geekstrident.com) # Created Date: 2023-08-29 15:28 ################################ start() letter(var_ftype, -)mixed_word(var_fperms)  digits(var_hard_links)  word(var_fowner)  letters(var_fgroup)  digits(var_fsize)  mixed_word(var_date_n_time, 2_group_occurrences)  mixed_words(var_file_name)  data(->, or_empty)  mixed_words(var_real_file, or_empty) end() -> record                        

And, the technical TextFSM template which is generated from user template snippet is

# Non-commercial use: generated by TemplateApp v.0.1.11 (geekstrident.com) # Created Date: 2023-08-29 16:15 ################################ Value ftype (([a-zA-Z])|-) Value fperms ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value hard_links (\d+) Value fowner ([a-zA-Z][a-zA-Z0-9]*) Value fgroup ([a-zA-Z]+) Value fsize (\d+) Value date_n_time ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( +[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){2}) Value file_name ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Value real_file ((([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*)|)) Start  ^${ftype}${fperms} +${hard_links} +${fowner} +${fgroup} +${fsize} +${date_n_time} +${file_name}\s*(->|)\s*${real_file}$$ -> Record                        
Therefore, the unintended action based errors could be reduced if technical work should translate to human readable language such as using plain text language to describe or to solve problem.

Assuming user1 is given an assignment to create a TextFSM template to parse the given output.  The template should be able to parse values of name, description, pid, vid, and sn.

NAME: "1234 chassis", DESCR: "1234 - Chassis" PID:                   , VID: 0xFF, SN: FF1234AA                        

Assuming user1 wants to use WebTemplate application to solve this problem.  User1 analyzes the output and find out that the first line has two categories: name and description, and the second line has three categories: pid, vid, and sn.  Therefore, a total number of categories using for this output is 3.

Iteration #1: Generated Snippet By Using Test Data
Builder - Example 2: Iteration #1

User1 observes that generated template snippet can parse most of data, but they are not expected results.  The first step is to rename the proper variable names.

Iteration #2: Renaming Variables
Builder - Example 2: Iteration #2

Iteration #3: Fixing "name" Column
Analyzing: name category is enclosed by double quote and it has at least one word.           It separates other category by comma. Current --------------  Snippet  : mixed_word(var_name)  Value    : "1234  Post Data: '  chassis"' Need To Change --------------  Expected Snippet: "mixed_words(var_name)"  Expected Value  : 1234 chassis  Other Action    : remove post data, i.e., '  chassis"' text                            
Builder - Example 2: Iteration #3
Iteration #4: Fixing "description" Column
Analyzing: description category is enclosed by double quote and           it has at least one word and might have punctuation between words. Current --------------  Snippet  : non_whitespaces_phrase(var_description)  Value    : "1234 - Chassis"  Post Data: Need To Change --------------  Expected Snippet: "non_whitespaces_phrase(var_description)"  Expected Value  : 1234 - Chassis  Other Action    :                            
Builder - Example 2: Iteration #4
Iteration #5: Fixing "pid" Column
Analyzing: pid category is an identification type and it requires one word.           pid for this case is empty.  That means pid can be a word or empty string.           It separates other category by comma. Current --------------  Snippet  : punct(var_pid)  Value    : ,  Post Data: Need To Change --------------  Expected Snippet: mixed_word(var_pid, or_empty)  Expected Value  : empty string  Other Action    : append '  ,'                            
Builder - Example 2: Iteration #5
Iteration #6: Fixing "vid" Column
Analyzing: vid category is an identification type and it requires one word.           It separates other category by comma. Current --------------  Snippet  : mixed_word(var_pid)  Value    :  Post Data: Need To Change --------------  Expected Snippet: mixed_word(var_pid)  Expected Value  : 0xFF  Other Action    : append ','                            
Builder - Example 2: Iteration #6
Iteration #7: Test Other Data By Using Generated Snippet
Expecting Result: should PROPERLY parse data. Actual Result   : failed to parse "pid", "vid", and "sn" columns                            
Builder - Example 2: Iteration #7
Iteration #8: Fixing Uncaptured Data for "pid", "vid", and "sn" columns
Case 1: "pid" Column  Current generated snippet for this category should work with this new data.   ---------- Case 2: "vid" Column  Previous value of this category: "0xFF,"    + Interpretation: <value><comma-separator>  New value of this category     : ""    + Interpretation: <value><space or multi-space><comma-separator>  Action: insert zero_or_spaces() keyword between snippet and command separator.    + Interpretation: <value>zero_or_spaces()<comma-separator> ---------- Case 3: "sn" Column  Previous value of this category: FF1234AA  New Value of this category     : empty string  Action: add or_empty keyword argument in snippet                                
Builder - Example 2: Iteration #8
Iteration #9: Improving and Running Multiple Test Data
Improvement #1: append end(space) keyword to line #1 to ensure trailing spaces are accepted. Improvement #2: append end(space) keyword to line #2 to ensure trailing spaces are accepted. Improvement #3: append " -> record" text to line #2 to parse multiple data set.                            
Builder - Example 2: Iteration #9

Assuming the 9th iteration is the final solution.  It should be proficient work if there is a verification process for designing or solving problem to reduce guessing or to make assumption.

Therefore, the unintended memory based errors could be reduced if there is verification process in software development.

This type of error can be

  • Intended Rule Based Mistake
  • Intended Knowledge Based Mistake
  • Human Use Error
  • Latent Error
  • ...

and, the practical approach of software development process are

  • Quality Assurance
  • Collaboration
  • Risk Management
  • Security
  • Reliability
  • Consistency
  • Proficiency
  • Scalability
  • Extensibility
  • Portability
  • Continuous Improvement
  • ...

TemplateApp needs to collaborate with other services to build high-quality software products.

Therefore, the intended error can be reduced if there is a workable strategy for development team.

Reusing Resource

NEITHER Geeks Trident Products NOR WebTemplate App SHALL NOT create base-line template snippet OR TextFSM template for end-users.  Furthermore, Geeks Trident SHALL NOT store any TextFSM template from end-users.  Therefore, the practice of REUSING VERIFIED SOLUTION SHOULD BE the practice or strategy of END-USER, DATA OWNERS, or ORGANIZATIONS.


TemplateApp lets individuals to reuse their own test data to build a GENERIC solution.  TemplateApp builder provides five features: , , , , and .

TemplateApp Builder - Differential

Differential feature lets end-user to build generic TextFSM template based on multiple different data where format of data should be a combination of <UNCHANGED-TEXT> and <CHANGED-TEXT>.
<UNCHANGED-TEXT> <CHANGED-TEXT> <UNCHANGED-TEXT> ... <CHANGED-TEXT> <UNCHANGED-TEXT> <CHANGED-TEXT> ... Where  UNCHANGED-TEXT MUST BE a text.  CHANGED-TEXT can be a text or empty string.   Note:  CHANGED-TEXT is often used to capture data.                
For example, assuming end-user needs to create TextFSM template that can parse "pid", "tty", "time", and "cmd" columns from Linux ps command line output.
 PID TTY           TIME CMD 12345 ttys000    0:00.55 /bin/zsh --login -i 3344 ttys001    0:01.23 -zsh                

To be able to build template snippet from test data by using TemplateApp Builder - Differential feature,

Step #1: Forming Generic Differential Format
The requirements of this assignment is parsing values of "pid", "tty", "time", and "cmd" columns. Therefore, capturing values of these columns should be  "pid" column should have "12345" and "3344" values.  "tty" column should have "ttys000" and "ttys001" values.  "time" column should have "0:00.55" and "0:01.23" values.  "cmd" column should have "/bin/zsh --login -i" and "-zsh" values. Based on syntax of differential format, layout of Linux ps command line is) ---------- <CHANGED-TEXT-of-pid> <CHANGED-TEXT-of-tty>    <CHANGED-TEXT-of-time> <CHANGED-TEXT-of-cmd> To make this layout transforming to differential format, it needs to modify by inserting <FILLING-UNCHANGED-TEXT> between <CHANGED-TEXT> such as ---------- <CHANGED-TEXT-of-pid> <FILING-UNCHANGED-TEXT> <CHANGED-TEXT-of-tty>  <FILING-UNCHANGED-TEXT>  <CHANGED-TEXT-of-time> <FILING-UNCHANGED-TEXT> <CHANGED-TEXT-of-cmd> Replacing <FILING-UNCHANGED-TEXT> with any text, let's substitute it with "|" The new differential format should be something similar to below which should satisfy the requirements of differential text, i.e. <CHANGED-TEXT> and <UNCHANGED-TEXT> combination. ---------- <CHANGED-TEXT-of-pid> | <CHANGED-TEXT-of-tty>  |  <CHANGED-TEXT-of-time> | <CHANGED-TEXT-of-cmd>                        
Step #2: Modifying Test Data and Substituting Into New Format
New format is ----------     <CHANGED-TEXT-of-pid> | <CHANGED-TEXT-of-tty>  |  <CHANGED-TEXT-of-time> | <CHANGED-TEXT-of-cmd> "pid" column ----------  Values   : "12345", "3344"  Is Diff  : Yes  Modifying: None   "tty" column ----------  Values   : "ttys000", "ttys000"  Is Diff  : Yes  Modifying: None   "time" column ----------  Values   : "0:00.55", "0:01.23"  Is Diff  : Yes  Modifying: None   "cmd" column ----------  Values   : "/bin/zsh --login -i", "-zsh"  Is Diff  : Yes  Modifying: optional look-ahead analysis    current values are command or command with flags which is separating by A BLANK-SPACE    Scenario A:      If command with flags which separate by A-BLANK-SPACE,        THEN          regex pattern formation for command separating by A-BLANK-SPACE        WOULD NOT MATCH          any command with flags which separate by more than ONE BLANK-SPACE.    Scenario B:      HOWEVER,      If other command with flags which separate by more than one BLANK-SPACE,        THEN          regex pattern formation for command separating by more than one BLANK-SPACE        WOULD MATCH          any command with flags which separate by A-BLANK-SPACE.        Therefore, it should be optimal if modifying      "/bin/zsh --login -i"      to      "/bin/zsh   --login -i New test data should be something similar to below ---------- 12345 | ttys000  |  0:00.55 | /bin/zsh   --login -i 3344 | ttys001  |  0:01.23 | -zsh                        
The generated snippet should be ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 01:29 ################################ start(space) digits(var_v0) | word(var_v1)  |  mixed_number(var_v2) | mixed_word_or_group(var_v3) end() -> record The test result should be ---------- Total-rows-count: 2 [{'v0': '12345', 'v1': 'ttys000', 'v2': '0:00.55', 'v3': '/bin/zsh   --login -i'}, {'v0': '3344', 'v1': 'ttys001', 'v2': '0:01.23', 'v3': '-zsh'}] or Total-rows-count: 2 +-------+---------+---------+-----------------------+ | v0    | v1      | v2      | v3                    | +-------+---------+---------+-----------------------+ | 12345 | ttys000 | 0:00.55 | /bin/zsh   --login -i | | 3344  | ttys001 | 0:01.23 | -zsh                  | +-------+---------+---------+-----------------------+                        
Step #3: Modifying Variable Names, Roll Back Original Format, and Testing
The generated snippet is ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 01:29 ################################ start(space) digits(var_v0) | word(var_v1)  |  mixed_number(var_v2) | mixed_word_or_group(var_v3) end() -> record The user snippet after modifying variable names ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 01:29 ################################ start(space) digits(var_pid) | word(var_tty)  |  mixed_number(var_time) | mixed_word_or_group(var_cmd) end() -> record                        
New format is ----------     <CHANGED-TEXT-of-pid> | <CHANGED-TEXT-of-tty>  |  <CHANGED-TEXT-of-time> | <CHANGED-TEXT-of-cmd> Original format is ----------     <CHANGED-TEXT-of-pid> <CHANGED-TEXT-of-tty>    <CHANGED-TEXT-of-time> <CHANGED-TEXT-of-cmd> The new user snippet after rollback to original format ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 01:29 ################################ start(space) digits(var_pid) word(var_tty)    mixed_number(var_time) mixed_word_or_group(var_cmd) end() -> record                        
The final user snippet is ----------     # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 01:29 ################################ start(space) digits(var_pid) word(var_tty)    mixed_number(var_time) mixed_word_or_group(var_cmd) end() -> record The generated TextFSM template is ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 01:52 ################################ Value pid (\d+) Value tty ([a-zA-Z][a-zA-Z0-9]*) Value time ([+\(\[\$-]?(\d+([,:/-]\d+)*)?[.]?\d+[\]\)%a-zA-Z]*) Value cmd ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( +[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Start  ^ *${pid} +${tty} +${time} ${cmd}$$ -> Record   The raw test data (original test data) ----------  PID TTY           TIME CMD 12345 ttys000    0:00.55 /bin/zsh --login -i 3344 ttys001    0:01.23 -zsh ... Test result is ---------- Total-rows-count: 2 [{'pid': '12345', 'tty': 'ttys000', 'time': '0:00.55', 'cmd': '/bin/zsh --login -i'}, {'pid': '3344', 'tty': 'ttys001', 'time': '0:01.23', 'cmd': '-zsh'}] or Total-rows-count: 2 +-------+---------+---------+---------------------+ | pid   | tty     | time    | cmd                 | +-------+---------+---------+---------------------+ | 12345 | ttys000 | 0:00.55 | /bin/zsh --login -i | | 3344  | ttys001 | 0:01.23 | -zsh                | +-------+---------+---------+---------------------+                        
TemplateApp Builder - Category

Category feature lets end-user to build generic TextFSM template based on a single category text where format of data should be
... line #(m-1): outer category text - blab blab line #m: beginning data block of category text <TEXT><SEPARATOR><VALUE>      <TEXT><SEPARATOR><VALUE> .... <TEXT><SEPARATOR> <VALUE>     <TEXT><SEPARATOR> <VALUE> .... <TEXT> <SEPARATOR> <VALUE>    <TEXT> <SEPARATOR> <VALUE> .... line #n: ended data block of category text line #(n+1): outer category text - blab blab ... Where  TEXT MUST BE a text.  VALUE can be a text or empty string.  SEPARATOR is often punctuation mark such as ":" symbol.                

For example, assuming end-user wants to capture meats, fruits, and drinks values in this data set

line 0: blab blab, i.e., outer text line 1: starting from here meats: chicken, pork    fruits: mango, peach drinks: coca, pesi line 4: ended category text line 5: bla blab, i.e., outer text                
Step #1: Preparing Parameters for Builder From Test Data
1) Paste test data in Test-Data of Builder - Category 2) Determine separator and entry it in separator text box.   In this case, take no action because separator is the default separator, i.e., : symbol. 3) Determine total number of category and entry it in category-count text box.   In this case, total-category-count is 2.  Enter 2 in category-count text box. 4) Determine any starting position and entry it in starting position text box if necessary.   In this case, starting position of category text is happening at line #1 5) Determine any ending position and entry it in ending position text box if necessary.   In this case, ending position of category text is happening at line #4                    
Step #2: Building Solution From Test Data and Testing
The Modification checkbox next to combo box must be unchecked and then filling The generated snippet should be
# Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 09:43 ################################ line 1: starting from here -> Table Table meats: mixed_phrase(var_meats)  fruits: mixed_phrase(var_fruits) drinks: mixed_phrase(var_drinks) line 4: ended category text -> EOF                        
The generated TextFSM template should be
# Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 09:43 ################################ Value meats ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)+) Value fruits ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)+) Value drinks ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)+) Start  ^line 1: starting from here -> Table Table  ^meats: ${meats} +fruits: ${fruits}  ^drinks: ${drinks}  ^line 4: ended category text -> EOF                        
The test result should be
Total-rows-count: 1 [{'meats': 'chicken, pork', 'fruits': 'mango, peach', 'drinks': 'coca, pesi'}] or Total-rows-count: 1 +---------------+--------------+------------+ | meats         | fruits       | drinks     | +---------------+--------------+------------+ | chicken, pork | mango, peach | coca, pesi | +---------------+--------------+------------+                        
Step #3: Modifying Snippet and Retesting If Necessary
1) Review, modify snippet, and retest if necessary Note: Remember to check Modification checkbox if modifying is needed.                    
TemplateApp Builder - Tabular

Tabular feature lets end-user to build generic TextFSM template based on a single tabular data.

Regular Tabular Text
... line #(m-1): outer tabular text - blab blab line #m: beginning data block of tabular text <HEADER1>    <HEADER2>    <HEADER3>     ... ------------ ------------ ------------- ... <VALUE1.1>   <VALUE1.2>   <VALUE1.3>    ... <VALUE2.1>   <VALUE2.2>   <VALUE2.3>    ... ... <VALUE#.1>   <VALUE#.2>   <VALUE#.3>    ... line #n: ended data block of tabular text line #(n+1): outer tabular text - blab blab ... Where  <HEADER#> line can be present or without headers line.  ------------ ------------ ------------- is a-group-of-symbols-line.  a-group-of-symbols-line can be present or without it.  <VALUE#.#> can be any text or empty.                
Tabular Parameters:  total-columns-count: #,  i.e., any number is greater than 0.  list-of-column-widths: #, #, #, #,      For example, tabular text has three columns where          first column width is 10,          second column width is 20,          third column width is 12      entry "10,20,12" or "10,20," in list-of-column-widths textbox.  starting-from: #        Default value is empty, that means tabular data is starting from line zero.  ending-to: #        Default value is empty, that means tabular data is ended at the end of the last line.    header-data: (-)?# or text.      Default value is empty, that means tabular data has headers line(s).      If starting-from is empty, headers-line is line zero.      If tabular data does not header line.  Entry negative number such as -1                                          or entry "headerless"      If a-group-of-symbols-line is present, headers line WOULD determine from      starting-from position to (position of a-group-of-symbols-line) - 1.        For example, position of starting-from line is line #0.                     position of a-group-of-symbols-line is line #2.                     headers of tabular WOULD construct from line #0 to line #1.  divider: separator to distinguish tabular columns.  There are three divider types    - a blank-space, i.e., " "    - multi-blank-space, i.e., "  "    - custom punctuation.  Usually, "|"                
There are five references for building tabular snippet  1) Using list-of-column-widths as a reference:     Mandatory parameters: list-of-column-widths and total-columns-count     Optional parameters : starting-from, ending-to, or header-data  2) Using a-group-of-symbols-line as a reference:     Mandatory parameters: N/A (but tabular text MUST have a-group-of-symbols-line)     Optional parameters : starting-from, ending-to, or header-data  3) Using multi-blank-space divider as a reference:     Mandatory parameters: total-columns-count and divider WOULD set to "double blank space"     Optional parameters : starting-from, ending-to, or header-data  4) Using a-blank-space divider as a reference:     Mandatory parameters: total-columns-count and divider WOULD set to "double blank space"     Optional parameters : starting-from, ending-to, or header-data  5) Using custom divider as a reference:     Mandatory parameters: total-columns-count and customer divider     Optional parameters : starting-from, ending-to, or header-data                
Assuming tabular test data is ---------- a        b       c -------- ------- ---------- val1.1   val1.2  val1.3 val3.1           val3.3         val4.2  val4.3                 val7.3 val2.1   val2.2 val5.1         val6.2         Using a-group-of-symbols-line as a reference to build template The generated snippet should be ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 14:24 ################################ a        b       c start() mixed_word(var_a)  mixed_word(var_b)  mixed_word(var_c) end(space) -> record start() mixed_word(var_a)  mixed_word(var_b) end(space) -> record start() mixed_word(var_a) space(repetition_7_10) mixed_word(var_c) end(space) -> record start() mixed_word(var_a) end(space) -> record start() space(repetition_6_8) mixed_word(var_b)  mixed_word(var_c) end(space) -> record start() space(repetition_6_8) mixed_word(var_b) end(space) -> record start() space(repetition_13_15) mixed_word(var_c) end(space) -> record The generated TextFSM template should be ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 14:24 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value c ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Start  ^a +b +c  ^${a} +${b} +${c} *$$ -> Record  ^${a} +${b} *$$ -> Record  ^${a}  {7,10} ${c} *$$ -> Record  ^${a} *$$ -> Record  ^ {6,8} ${b} +${c} *$$ -> Record  ^ {6,8} ${b} *$$ -> Record  ^ {13,15} ${c} *$$ -> Record   The test result should be ---------- Total-rows-count: 6 [{'a': 'val1.1', 'b': 'val1.2', 'c': 'val1.3'}, {'a': 'val3.1', 'b': 'val3.3', 'c': ''}, {'a': '', 'b': 'val4.2', 'c': 'val4.3'}, {'a': 'val2.1', 'b': 'val2.2', 'c': ''}, {'a': 'val5.1', 'b': '', 'c': ''}, {'a': '', 'b': 'val6.2', 'c': ''}] or Total-rows-count: 6 +--------+--------+--------+ | a      | b      | c      | +--------+--------+--------+ | val1.1 | val1.2 | val1.3 | | val3.1 | val3.3 |        | |        | val4.2 | val4.3 | | val2.1 | val2.2 |        | | val5.1 |        |        | |        | val6.2 |        | +--------+--------+--------+                

TemplateApp Builder - Tabular feature currently supports two user-marking types.


Case 1: Tabular Text with Long Value or Out of Boundary in First Column
... line #(m-1): outer tabular text - blab blab line #m: beginning data block of tabular text <HEADER1>    <HEADER2>    <HEADER3>     ... ------------ ------------ ------------- ... <VALUE1.1>   <VALUE1.2>   <VALUE1.3>    ... ... <VALUE#K.1-LONG-TEXT>             <VALUE#K.2>  <VALUE#K.3>   ... ... <VALUE#.1>   <VALUE#.2>   <VALUE#.3>    ... line #n: ended data block of tabular text line #(n+1): outer tabular text - blab blab ... Where  <HEADER#> line can be present or without headers line.  a-group-of-symbols-line can be present or without it.  <VALUE#.#> can be any text or empty.  Sometimes, a length of <VALUE#K.1>  in first column is bigger than its width so that tabular text is lay out  the remaining values of the following cells to next line.                        
To solve this case, end-users need to mark <user-marker-one-line>.  For example,
Assuming tabular test data is ========== a        b       c -------- ------- ------------- val1.1   val1.2  val1.3 val2.1-long-text         val2.2  val2.3 val3.1   val3.2  val3.3 The modified test data after marking, ========== a        b       c -------- ------- ------------- val1.1   val1.2  val1.3 <user-marker-one-line>val2.1-long-text         val2.2  val2.3 val3.1   val3.2  val3.3 The generated snippet is ========== # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 15:00 ################################ a        b       c start() mixed_word(var_a) end(space) -> Next start() space(repetition_7_9) mixed_word(var_b)  mixed_word(var_c) end() -> record start() mixed_word(var_a)  mixed_word(var_b)  mixed_word(var_c) end() -> record The generated TextFSM template is ========== # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 15:00 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value c ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Start  ^a +b +c  ^${a} *$$ -> Next  ^ {7,9} ${b} +${c}$$ -> Record  ^${a} +${b} +${c}$$ -> Record   The test result is ========== Total-rows-count: 3 [{'a': 'val1.1', 'b': 'val1.2', 'c': 'val1.3'}, {'a': 'val2.1-long-text', 'b': 'val2.2', 'c': 'val2.3'}, {'a': 'val3.1', 'b': 'val3.2', 'c': 'val3.3'}] or Total-rows-count: 3 +------------------+--------+--------+ | a                | b      | c      | +------------------+--------+--------+ | val1.1           | val1.2 | val1.3 | | val2.1-long-text | val2.2 | val2.3 | | val3.1           | val3.2 | val3.3 | +------------------+--------+--------+                
Case 2: Tabular Text with Too Many Values in Last Column
... line #(m-1): outer tabular text - blab blab line #m: beginning data block of tabular text <HEADER1>    <HEADER2>    ... <HEADER#N> ------------ ------------ ... ------------- <VALUE1.1>   <VALUE1.2>   ... <VALUE1.N> ... <VALUE#K.1>  <VALUE#K.2>  ... <VALUE#K.N>                              <VALUE#K.(N+1)>                              <VALUE#K.(N+2)>                              ... ... <VALUE#.1>   <VALUE#.2>   ... <VALUE#.N> line #n: ended data block of tabular text line #(n+1): outer tabular text - blab blab ... Where  <HEADER#> line can be present or without headers line.  a-group-of-symbols-line can be present or without it.  <VALUE#.#> can be any text or empty.  Sometimes, last column contains cell  has so many values so that tabular text is layout continues-value(s) of  last cell in next line or next multiple lines.                        
To solve this case, end-users need to mark <user-marker-multi-line>.  For example,
Assuming tabular test data is ========== a        b       c -------- ------- ------------- val1.1   val1.2  val1.3 val2.1   val2.2  val2.3                 continue-val2.4                 continue-val2.5 val3.1   val3.2  val3.3 The modified test data after marking, ========== a        b       c -------- ------- ------------- val1.1   val1.2  val1.3 <user-marker-multi-line>val2.1   val2.2  val2.3                 continue-val2.4                 continue-val2.5 val3.1   val3.2  val3.3 The generated snippet is ========== # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 15:58 ################################ a        b       c start() mixed_word()zero_or_spaces() -> continue.record start() mixed_word(var_a)  mixed_word(var_b)  mixed_word(var_c, meta_data_list) end(space) -> continue start() space(repetition_13_19) mixed_word(var_c, meta_data_list) end(space) -> continue The generated TextFSM template is ========== # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-21 15:58 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value List c ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Start  ^a +b +c  ^[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]* * -> Continue.Record  ^${a} +${b} +${c} *$$ -> Continue  ^ {13,19} ${c} *$$ -> Continue The test result is ========== Total-rows-count: 3 [{'a': 'val1.1', 'b': 'val1.2', 'c': ['val1.3']}, {'a': 'val2.1', 'b': 'val2.2', 'c': ['val2.3', 'continue-val2.4', 'continue-val2.5']}, {'a': 'val3.1', 'b': 'val3.2', 'c': ['val3.3']}] or Total-rows-count: 3 +--------+--------+--------------------------------------------------+ | a      | b      | c                                                | +--------+--------+--------------------------------------------------+ | val1.1 | val1.2 | ['val1.3']                                       | | val2.1 | val2.2 | ['val2.3', 'continue-val2.4', 'continue-val2.5'] | | val3.1 | val3.2 | ['val3.3']                                       | +--------+--------+--------------------------------------------------+                
TemplateApp Builder - Free Style

SHOULD BE AVAILABLE in BETA VERSION

TemplateApp Builder - Designing

SHOULD BE AVAILABLE in BETA VERSION

Low Learning Curve

TemplateApp employs principle of least effort to boost individuals to quickly recognize problem and confidently build solution.  Builder features are generated GENERIC solution which is based on human analogy.

For example, assuming user1 has created a solution to parse the output of "ls - l" Linux command line such as
Output of "ls -l" Linux command line ---------- lrwxr-xr-x  1 user1  staff   13 Sep 23 11:43 test_script -> ../test_ws.py -rw-r--r--  1 user1  staff  748 Jul  5 12:13 test_ws.c User snippet solution for "ls -l" Linux command line Let's call this solution as snippet_to_parse_linux_ls_l_output ---------- start() letter(var_ftype, -)mixed_word(var_fperms)  digits(var_hard_links)  word(var_fowner)  letters(var_fgroup)  digits(var_fsize)  mixed_word(var_date_n_time, 2_group_occurrences)  mixed_words(var_file_name)  data(->, or_empty)  mixed_words(var_real_file, or_empty) end() -> record                 Generated TextFSM template Let's call this solution as textfsm_template_to_parse_linux_ls_l_output ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-23 00:05 ################################ Value ftype (([a-zA-Z])|-) Value fperms ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value hard_links (\d+) Value fowner ([a-zA-Z][a-zA-Z0-9]*) Value fgroup ([a-zA-Z]+) Value fsize (\d+) Value date_n_time ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( +[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){2}) Value file_name ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Value real_file (((([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*))|)) Start  ^${ftype}${fperms} +${hard_links} +${fowner} +${fgroup} +${fsize} +${date_n_time} +${file_name}\s*(->|)\s*${real_file}$$ -> Record Test Result is ---------- Total-rows-count: 2 [{'ftype': 'l', 'fperms': 'rwxr-xr-x', 'hard_links': '1', 'fowner': 'user1', 'fgroup': 'staff', 'fsize': '13', 'date_n_time': 'Sep 23 11:43', 'file_name': 'test_script', 'real_file': '../test_ws.py'}, {'ftype': '-', 'fperms': 'rw-r--r--', 'hard_links': '1', 'fowner': 'user1', 'fgroup': 'staff', 'fsize': '748', 'date_n_time': 'Jul  5 12:13', 'file_name': 'test_ws.c', 'real_file': ''}] or Total-rows-count: 2 +-------+-----------+------------+--------+--------+-------+--------------+-------------+---------------+ | ftype | fperms    | hard_links | fowner | fgroup | fsize | date_n_time  | file_name   | real_file     | +-------+-----------+------------+--------+--------+-------+--------------+-------------+---------------+ | l     | rwxr-xr-x | 1          | user1  | staff  | 13    | Sep 23 11:43 | test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | user1  | staff  | 748   | Jul  5 12:13 | test_ws.c   |               | +-------+-----------+------------+--------+--------+-------+--------------+-------------+---------------+                
User1 decides to give the generated TextFSM template (i.e., textfsm_template_to_parse_linux_ls_l_output) to development team and other groups to test and provide feedback.  User-k from other group adds comment
textfsm_template_to_parse_linux_ls_l_output template can parse the correct result for "ls -l" Unix--like command line of Darwin kernel (i.e., Unix Operating System of macOS) ---------- lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13 test_ws.c Test Result is ---------- Total-rows-count: 2 [{'ftype': 'l', 'fperms': 'rwxr-xr-x', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '13', 'date_n_time': 'Sep 23 11:43', 'file_name': 'test_script', 'real_file': '../test_ws.py'}, {'ftype': '-', 'fperms': 'rw-r--r--', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '748', 'date_n_time': 'Jul  5 12:13', 'file_name': 'test_ws.c', 'real_file': ''}] or Total-rows-count: 2 +-------+-----------+------------+--------+--------+-------+--------------+-------------+---------------+ | ftype | fperms    | hard_links | fowner | fgroup | fsize | date_n_time  | file_name   | real_file     | +-------+-----------+------------+--------+--------+-------+--------------+-------------+---------------+ | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43 | test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13 | test_ws.c   |               | +-------+-----------+------------+--------+--------+-------+--------------+-------------+---------------+                
HOWEVER, textfsm_template_to_parse_linux_ls_l_output template is able to parse the result for "ls -lT" Unix--like command line of macOS, but with UNEXPECTED DATA in "file_name" column. ---------- lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43:44 2023 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13:13 2023 test_ws.c Test Result is ---------- Total-rows-count: 2 [{'ftype': 'l', 'fperms': 'rwxr-xr-x', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '13', 'date_n_time': 'Sep 23 11:43:44', 'file_name': '2023 test_script', 'real_file': '../test_ws.py'}, {'ftype': '-', 'fperms': 'rw-r--r--', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '748', 'date_n_time': 'Jul  5 12:13:13', 'file_name': '2023 test_ws.c', 'real_file': ''}] or Total-rows-count: 2 +-------+-----------+------------+--------+--------+-------+-----------------+------------------+---------------+ | ftype | fperms    | hard_links | fowner | fgroup | fsize | date_n_time     | file_name        | real_file     | +-------+-----------+------------+--------+--------+-------+-----------------+------------------+---------------+ | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43:44 | 2023 test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13:13 | 2023 test_ws.c   |               | +-------+-----------+------------+--------+--------+-------+-----------------+------------------+---------------+                
Userk asks user1 to support solution for macOS command line.
First, user1 combining current test data with userk's test data, and then perform testing ---------- lrwxr-xr-x  1 user1  staff   13 Sep 23 11:43 test_script -> ../test_ws.py -rw-r--r--  1 user1  staff  748 Jul  5 12:13 test_ws.c lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13 test_ws.c lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43:44 2023 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13:13 2023 test_ws.c Test Result is ---------- Total-rows-count: 6 +-------+-----------+------------+--------+--------+-------+-----------------+------------------+---------------+ | ftype | fperms    | hard_links | fowner | fgroup | fsize | date_n_time     | file_name        | real_file     | +-------+-----------+------------+--------+--------+-------+-----------------+------------------+---------------+ | l     | rwxr-xr-x | 1          | user1  | staff  | 13    | Sep 23 11:43    | test_script      | ../test_ws.py | | -     | rw-r--r-- | 1          | user1  | staff  | 748   | Jul  5 12:13    | test_ws.c        |               | | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43    | test_script      | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13    | test_ws.c        |               | | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43:44 | 2023 test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13:13 | 2023 test_ws.c   |               | +-------+-----------+------------+--------+--------+-------+-----------------+------------------+---------------+                        
After testing and review, user1 concludes
The content of "ls -l" command for both Linux and Unix-like macOS has the same structure. Therefore, textfsm_template_to_parse_linux_ls_l_output template can reuse to parse result for "ls -l" command line. The content structure of "ls -lT" command of Unix-like macOS has addition column that hold year-value of file timestamp. Therefore, snippet_to_parse_linux_ls_l_output needs to modify to meet requirement of "ls -lT" command.                
user1 proposes two solutions to userk.

Solution A: Using Separated Solution for Each Command
Reusing textfsm_template_to_parse_linux_ls_l_output for "ls -l" command of Unix-like macOS Using below snippet solution to generate TextFSM template for "ls -lT" command of Unix-like macOS ---------- start() letter(var_ftype, -)mixed_word(var_fperms)  digits(var_hard_links)  word(var_fowner)  letters(var_fgroup)  digits(var_fsize)  mixed_word(var_date_n_time, 3_group_occurrences)  mixed_words(var_file_name)  data(->, or_empty)  mixed_words(var_real_file, or_empty) end() -> record Generated TextFSM template that dedicate to parse result for only "ls -lT" ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-24 11:20 ################################ Value ftype (([a-zA-Z])|-) Value fperms ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value hard_links (\d+) Value fowner ([a-zA-Z][a-zA-Z0-9]*) Value fgroup ([a-zA-Z]+) Value fsize (\d+) Value date_n_time ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( +[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){3}) Value file_name ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Value real_file (((([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*))|)) Start  ^${ftype}${fperms} +${hard_links} +${fowner} +${fgroup} +${fsize} +${date_n_time} +${file_name}\s*(->|)\s*${real_file}$$ -> Record   Test Data of "ls -lT" ---------- lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43:44 2023 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13:13 2023 test_ws.c Test result is ---------- Total-rows-count: 2 [{'ftype': 'l', 'fperms': 'rwxr-xr-x', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '13', 'date_n_time': 'Sep 23 11:43:44 2023', 'file_name': 'test_script', 'real_file': '../test_ws.py'}, {'ftype': '-', 'fperms': 'rw-r--r--', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '748', 'date_n_time': 'Jul  5 12:13:13 2023', 'file_name': 'test_ws.c', 'real_file': ''}] or Total-rows-count: 2 +-------+-----------+------------+--------+--------+-------+----------------------+-------------+---------------+ | ftype | fperms    | hard_links | fowner | fgroup | fsize | date_n_time          | file_name   | real_file     | +-------+-----------+------------+--------+--------+-------+----------------------+-------------+---------------+ | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43:44 2023 | test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13:13 2023 | test_ws.c   |               | +-------+-----------+------------+--------+--------+-------+----------------------+-------------+---------------+                        
Solution B: Using Optimal Flexible Solution for Either ls -l or ls -T
User snippet for both "ls -l" or "ls -lT" ---------- start() letter(var_ftype, -)mixed_word(var_fperms)  digits(var_hard_links)  word(var_fowner)  letters(var_fgroup)  digits(var_fsize)  mixed_word(var_date_n_time, 2_group_occurrences) digit(var_year, repetition_4, or_empty) mixed_words(var_file_name)  data(->, or_empty)  mixed_words(var_real_file, or_empty) end() -> record Generated TextFSM template that should parse result for both "ls -l" or "ls -lT" ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-24 10:54 ################################ Value ftype (([a-zA-Z])|-) Value fperms ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value hard_links (\d+) Value fowner ([a-zA-Z][a-zA-Z0-9]*) Value fgroup ([a-zA-Z]+) Value fsize (\d+) Value date_n_time ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( +[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){2}) Value year (((\d{4})|)) Value file_name ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Value real_file (((([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*))|)) Start  ^${ftype}${fperms} +${hard_links} +${fowner} +${fgroup} +${fsize} +${date_n_time}\s*${year} ${file_name}\s*(->|)\s*${real_file}$$ -> Record Test data of "ls -l" and "ls -lT" ---------- lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13 test_ws.c lrwxr-xr-x  1 userk  staff   13 Sep 23 11:43:44 2023 test_script -> ../test_ws.py -rw-r--r--  1 userk  staff  748 Jul  5 12:13:13 2023 test_ws.c Test result is ---------- Total-rows-count: 4 [{'ftype': 'l', 'fperms': 'rwxr-xr-x', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '13', 'date_n_time': 'Sep 23 11:43', 'year': '', 'file_name': 'test_script', 'real_file': '../test_ws.py'}, {'ftype': '-', 'fperms': 'rw-r--r--', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '748', 'date_n_time': 'Jul  5 12:13', 'year': '', 'file_name': 'test_ws.c', 'real_file': ''}, {'ftype': 'l', 'fperms': 'rwxr-xr-x', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '13', 'date_n_time': 'Sep 23 11:43:44', 'year': '2023', 'file_name': 'test_script', 'real_file': '../test_ws.py'}, {'ftype': '-', 'fperms': 'rw-r--r--', 'hard_links': '1', 'fowner': 'userk', 'fgroup': 'staff', 'fsize': '748', 'date_n_time': 'Jul  5 12:13:13', 'year': '2023', 'file_name': 'test_ws.c', 'real_file': ''}] or Total-rows-count: 4 +-------+-----------+------------+--------+--------+-------+-----------------+------+-------------+---------------+ | ftype | fperms    | hard_links | fowner | fgroup | fsize | date_n_time     | year | file_name   | real_file     | +-------+-----------+------------+--------+--------+-------+-----------------+------+-------------+---------------+ | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43    |      | test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13    |      | test_ws.c   |               | | l     | rwxr-xr-x | 1          | userk  | staff  | 13    | Sep 23 11:43:44 | 2023 | test_script | ../test_ws.py | | -     | rw-r--r-- | 1          | userk  | staff  | 748   | Jul  5 12:13:13 | 2023 | test_ws.c   |               | +-------+-----------+------------+--------+--------+-------+-----------------+------+-------------+---------------+                        
If userk decides to use Solution-A, user1 MIGHT provide the least effort to explain to userk such as mixed_word(var_date_n_time, 2_group_occurrences) would capture exact three mixed words  e.g., Sep 23 11:43        Jul  5 12:13     However, output of "ls -lT" produces extra "year" data, they are Sep 23 11:43:44 2023                                                        Jul  5 12:13:13 2023 To capture exact four mixed words for date_n_time variable, just modify 2 to 3 or mixed_word(var_date_n_time, 3_group_occurrences)                
If userk decides to use Solution-B, user1 MIGHT provide the least effort to explain to userk such as output of "ls -lT" produces extra "year" data, they are Sep 23 11:43:44 2023                                                        Jul  5 12:13:13 2023 If both "ls -l" and "ls -lT" is presented as tabular data,   the interpretion of "ls -l" is the tabular data of 10 columns where "year" column has all empty value   WHILE   the interpretion of "ls -lT" is the tabular data of 10 columns where "year" has some values which are four digits. Therefore, the properties of the "year" column are  - MUST either have value or empty.  - A value MUST be four digits.  - A value MUST be next to date_n_time column. As a result, it should be created and inserted right after date_n_time column as ... mixed_word(var_date_n_time, 2_group_occurrences) digit(var_year, repetition_4, or_empty) mixed_words(var_file_name) ...                



Next example shall use deductive reasoning to troubleshot problem.

Assuming end-user group finds some added-value in TextFSM template and really want to apply it to improve quality and productivity for their products.  However, end-user group faces some challenges
TextFSM MIGHT requires  - Understanding TextFSM and ability to create TextFSM template.  - Understanding Python regular expression and ability to use it.   End-user Group's Advantage  - Surplus knowlege of test data.  In other words, all members (i.e., 100%) of end-user group have solid knowledge about test data.   End-user Group's Disadvantage  - Around 3% members understand TextFSM and can build its template.  - Around 5% members understand Python regular express and know how to apply it.   End-user Group Expectation  - Expecting all members can confidently create TextFSM templates and use them daily.   End-user Group's Challenges  - Needing a TextFSM training for 97-100% members.  - Needing a Python regular express training for 95-100% members.  - Training process should help    + To accelerate time to proficiency.    + To increase knowledge retention.    + To boost quality and productivity.    + To deliver consistent outcome.    + Etc.                
To deal with these challenges, end-user group asks one member to research and to evaluate any product that can solve these challenges.  Let's call member's name is Tomy who have some TextFSM experience.  Furthermore, Tomy is a Python regular expression expertise. Assuming Tomy finds out that TemplateApp can solves these challenges and begins to evaluate it

Studying Test Data #1
Test Data ---------- a        b       -------- ------- val1.1   val1.2         val2.2 val3.1 val4.1   val4.2 val5.1 Generated Snippet by Using Template Builder - Tabular - Symbol Divider ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 10:50 ################################ a        b       start() mixed_word(var_a)  mixed_word(var_b) end(space) -> record start() mixed_word(var_a) end(space) -> record start() space(repetition_7_9) mixed_word(var_b) end(space) -> record Generated Template is ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 10:50 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Start  ^a +b  ^${a} +${b} *$$ -> Record  ^${a} *$$ -> Record  ^ {7,9} ${b} *$$ -> Record   Test Result is ---------- Total-rows-count: 5 [{'a': 'val1.1', 'b': 'val1.2'}, {'a': '', 'b': 'val2.2'}, {'a': 'val3.1', 'b': ''}, {'a': 'val4.1', 'b': 'val4.2'}, {'a': 'val5.1', 'b': ''}] or Total-rows-count: 5 +--------+--------+ | a      | b      | +--------+--------+ | val1.1 | val1.2 | |        | val2.2 | | val3.1 |        | | val4.1 | val4.2 | | val5.1 |        | +--------+--------+                        
Studying Test Data #2
Test Data ---------- a        b       -------- ------- val1.1   1.2         2.2 val3.1 val4.1   4.2 val5.1 Generated Snippet by Using Template Builder - Tabular - Symbol Divider ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 11:17 ################################ a        b       start() mixed_word(var_a)  number(var_b) end(space) -> record start() mixed_word(var_a) end(space) -> record start() space(repetition_7_9) number(var_b) end(space) -> record Generated Template is ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 11:17 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b (\d*[.]?\d+) Start  ^a +b  ^${a} +${b} *$$ -> Record  ^${a} *$$ -> Record  ^ {7,9} ${b} *$$ -> Record Test Result is ---------- Total-rows-count: 5 [{'a': 'val1.1', 'b': '1.2'}, {'a': '', 'b': '2.2'}, {'a': 'val3.1', 'b': ''}, {'a': 'val4.1', 'b': '4.2'}, {'a': 'val5.1', 'b': ''}] or Total-rows-count: 5 +--------+-----+ | a      | b   | +--------+-----+ | val1.1 | 1.2 | |        | 2.2 | | val3.1 |     | | val4.1 | 4.2 | | val5.1 |     | +--------+-----+                        
Studying Test Data #1.1 - with Comment Column
Test Data ---------- a        b       comment -------- ------- -------------------- val1.1   val1.2  first_line_has_all_values         val2.2  second line has 1st empty cell. val3.1           third line has 2nd empty cell. val4.1   val4.2  fourth line has all values. val5.1           fifth line has 2nd empty cell. Generated Snippet by Using Template Builder - Tabular - Symbol Divider ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 14:35 ################################ a        b       comment start() mixed_word(var_a)  mixed_word(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() mixed_word(var_a) space(repetition_8_11) mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() space(repetition_7_9) mixed_word(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record Generated Template is ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 14:35 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value comment ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){,5}) Start  ^a +b +comment  ^${a} +${b} +${comment}$$ -> Record  ^${a}  {8,11} ${comment}$$ -> Record  ^ {7,9} ${b} +${comment}$$ -> Record Test Result is ---------- Total-rows-count: 5 [{'a': 'val1.1', 'b': 'val1.2', 'comment': 'first_line_has_all_values'}, {'a': '', 'b': 'val2.2', 'comment': 'second line has 1st empty cell.'}, {'a': 'val3.1', 'b': 'third', 'comment': 'line has 2nd empty cell.'}, {'a': 'val4.1', 'b': 'val4.2', 'comment': 'fourth line has all values.'}, {'a': 'val5.1', 'b': 'fifth', 'comment': 'line has 2nd empty cell.'}] or Total-rows-count: 5 +--------+--------+---------------------------------+ | a      | b      | comment                         | +--------+--------+---------------------------------+ | val1.1 | val1.2 | first_line_has_all_values       | |        | val2.2 | second line has 1st empty cell. | | val3.1 | third  | line has 2nd empty cell.        | | val4.1 | val4.2 | fourth line has all values.     | | val5.1 | fifth  | line has 2nd empty cell.        | +--------+--------+---------------------------------+                
Reviewing Test Result of #1.1
Expecting result [  {'a': 'val1.1', 'b': 'val1.2', 'comment': 'first_line_has_all_values'},  {'a': '', 'b': 'val2.2', 'comment': 'second line has 1st empty cell.'},  {'a': 'val3.1', 'b': '', 'comment': 'third line has 2nd empty cell.'},  {'a': 'val4.1', 'b': 'val4.2', 'comment': 'fourth line has all values.'},  {'a': 'val5.1', 'b': '', 'comment': 'fifth line has 2nd empty cell.'} ] but, received unexpected value in "b" column and dropping the first word in "comment" column on row #3 and row #5. [  {'a': 'val1.1', 'b': 'val1.2', 'comment': 'first_line_has_all_values'},  {'a': '', 'b': 'val2.2', 'comment': 'second line has 1st empty cell.'},  {'a': 'val3.1', 'b': 'third', 'comment': 'line has 2nd empty cell.'},  {'a': 'val4.1', 'b': 'val4.2', 'comment': 'fourth line has all values.'},  {'a': 'val5.1', 'b': 'fifth', 'comment': 'line has 2nd empty cell.'} ]                
Studying Test Data #2.1 - with Comment Column
Test Data ---------- a        b       comment -------- ------- -------------------- val1.1   1.2     first_line_has_all_values         2.2     second line has 1st empty cell. val3.1           third line has 2nd empty cell. val4.1   4.2     fourth line has all values. val5.1           fifth line has 2nd empty cell. Generated Snippet by Using Template Builder - Tabular - Symbol Divider ---------- # Non-commercial use: generated by WebTemplate v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 15:18 ################################ a        b       comment start() mixed_word(var_a)  number(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() mixed_word(var_a) space(repetition_8_11) mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() space(repetition_7_9) number(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record Generated Template is ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 15:18 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b (\d*[.]?\d+) Value comment ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){,5}) Start  ^a +b +comment  ^${a} +${b} +${comment}$$ -> Record  ^${a}  {8,11} ${comment}$$ -> Record  ^ {7,9} ${b} +${comment}$$ -> Record Test Result is ---------- Total-rows-count: 5 [{'a': 'val1.1', 'b': '1.2', 'comment': 'first_line_has_all_values'}, {'a': '', 'b': '2.2', 'comment': 'second line has 1st empty cell.'}, {'a': 'val3.1', 'b': '', 'comment': 'third line has 2nd empty cell.'}, {'a': 'val4.1', 'b': '4.2', 'comment': 'fourth line has all values.'}, {'a': 'val5.1', 'b': '', 'comment': 'fifth line has 2nd empty cell.'}] or Total-rows-count: 5 +--------+-----+---------------------------------+ | a      | b   | comment                         | +--------+-----+---------------------------------+ | val1.1 | 1.2 | first_line_has_all_values       | |        | 2.2 | second line has 1st empty cell. | | val3.1 |     | third line has 2nd empty cell.  | | val4.1 | 4.2 | fourth line has all values.     | | val5.1 |     | fifth line has 2nd empty cell.  | +--------+-----+---------------------------------+                
Reviewing Test Result of #2.1
Even though the test result of #2.1 delivers the expected outcome, but Tomy CAN NOT figure out that this case is workable, but the other case (i.e., #1.1) produces incorrect result. # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 14:35 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value comment ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){,5}) Start  ^a +b +comment  ^${a} +${b} +${comment}$$ -> Record  ^${a}  {8,11} ${comment}$$ -> Record  ^ {7,9} ${b} +${comment}$$ -> Record # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-25 15:18 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b (\d*[.]?\d+) Value comment ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*){,5}) Start  ^a +b +comment  ^${a} +${b} +${comment}$$ -> Record  ^${a}  {8,11} ${comment}$$ -> Record  ^ {7,9} ${b} +${comment}$$ -> Record                        
Tomy tries several efforts to resolve incorrectness of parsing test data #1.1 such as  - manually create TextFSM template and test it.  - or ask other member to create TextFSM template and test it.  - or ask question in TextFSM forum for support. After a few tries with some solutions, unfortunately, problem is still persisted. ##### # Assuming, below template is manually created by # other member from Tomy group for test data #1.1 ##### Value a (\S+) Value b (\S+) Value comment (\S+( \S+)*) Start  ^a +b +comment  ^${a} +${b} +${comment}$$ -> Record  ^${a} {10,} ${comment}$$ -> Record  ^ {9,} ${b} +${comment}$$ -> Record ##### # Assuming, below template is manually created by # other member from Tomy group for test data #2.1 ##### Value a (\S+) Value b (\d+\.?\d*) Value comment (\S+( \S+)*) Start  ^a +b +comment  ^${a} +${b} +${comment}$$ -> Record  ^${a} {10,} ${comment}$$ -> Record  ^ {9,} ${b} +${comment}$$ -> Record                
Tomy discusses current situation with team and asks for open-minded ideas.  One member suggests,
Opinion: this problem is about text and how to process it. Is it possible to solve it by describing problem in plain text with logical thinking?                
Assuming Tomy agrees that this suggestion is reasonable and decides to hire Reasoning Analyzer or Reasoning Problem Solver.  Let's call this person name is Lucy.
Lucy  Strength  --------    - Ability to solve problem in plain text conversation with logical thinking.    Weakness  --------    - Aware about Python regular expression but don't know how it work.    - Have zero percent hand-on experience with Python regular expression.    Collaboration Requirements for Solving Problem  --------    - Need to provide problem statement in everyday conversation form with minimal technical details as much as possible.                
Tommy's Work ... Step #1: Converting Tech Work to Everyday Conversation ----------  Converting TextFSM template to everyday conversation might be complex.  Tomy finds out that user snippet which generated from TemplateApp  or WebTemplate is closed to human interpretion.  Tomy decides to reuse it.  To make snippet more easier to identify between tech work and  everyday conversation,  Tomy modifies user snippets in group of name mnemonics or chunking.    + Replacing a blank space to space()    + Replacing double spaces or multi-spaces to spaces()    + Comment column can have one word or more,        mixed_word(var_comment, at_most_5_phrase_occurrences) will limit from one to six mixed-words,        need to modify to        mixed_words(var_comment) which will satisfy at least one mixed-word.         User snippet for test data #1.1 ---------- a        b       comment start() mixed_word(var_a)  mixed_word(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() mixed_word(var_a) space(repetition_8_11) mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() space(repetition_7_9) mixed_word(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record After modifying into group of name mnemonics or chunking. a        b       comment start() mixed_word(var_a)spaces()mixed_word(var_b)spaces()mixed_words(var_comment) end() -> record start() mixed_word(var_a)space()space(repetition_8_11)space()mixed_words(var_comment) end() -> record start() space(repetition_7_9)space()mixed_word(var_b)spaces()mixed_words(var_comment) end() -> record User snippet for test data #2.1 ---------- a        b       comment start() mixed_word(var_a)  number(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() mixed_word(var_a) space(repetition_8_11) mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record start() space(repetition_7_9) number(var_b)  mixed_word(var_comment, at_most_5_phrase_occurrences) end() -> record After modifying into group of name mnemonics or chunking. a        b       comment start() mixed_word(var_a)spaces()number(var_b)spaces()mixed_words(var_comment) end() -> record start() mixed_word(var_a)space()space(repetition_8_11)space()mixed_words(var_comment) end() -> record start() space(repetition_7_9)space()number(var_b)spaces()mixed_words(var_comment) end() -> record                
Tommy's Work ... Step #2: Organizing Data For Statement Problem ---------- Given Two Set Of Test Data and User Snippet
test_data_1 ---------- a        b       comment -------- ------- -------------------- val1.1   val1.2  first_line_has_all_values         val2.2  second line has 1st empty cell. val3.1           third line has 2nd empty cell. val4.1   val4.2  fourth line has all values. val5.1           fifth line has 2nd empty cell. user_snippet_1 ---------- a        b       comment start() mixed_word(var_a)spaces()mixed_word(var_b)spaces()mixed_words(var_comment) end() -> record start() mixed_word(var_a)space()space(repetition_8_11)space()mixed_words(var_comment) end() -> record start() space(repetition_7_9)space()mixed_word(var_b)spaces()mixed_words(var_comment) end() -> record
test_data_2 ---------- a        b       comment -------- ------- -------------------- val1.1   1.2     first_line_has_all_values         2.2     second line has 1st empty cell. val3.1           third line has 2nd empty cell. val4.1   4.2     fourth line has all values. val5.1           fifth line has 2nd empty cell. user_snippet_2 ---------- a        b       comment start() mixed_word(var_a)spaces()number(var_b)spaces()mixed_words(var_comment) end() -> record start() mixed_word(var_a)space()space(repetition_8_11)space()mixed_words(var_comment) end() -> record start() space(repetition_7_9)space()number(var_b)spaces()mixed_words(var_comment) end() -> record
Problem XYZ system takes pair(test_data_1, user_snippet1) and expect to produce below result +--------+--------+---------------------------------+ | a      | b      | comment                         | +--------+--------+---------------------------------+ | val1.1 | val1.2 | first_line_has_all_values       | |        | val2.2 | second line has 1st empty cell. | | val3.1 |        | third line has 2nd empty cell.  | | val4.1 | val4.2 | fourth line has all values.     | | val5.1 |        | fifth line has 2nd empty cell.  | +--------+--------+---------------------------------+ but the actual result is that some values are removed from "comment" column and those removed values are inserted into "b" column. +--------+--------+---------------------------------+ | a      | b      | comment                         | +--------+--------+---------------------------------+ | val1.1 | val1.2 | first_line_has_all_values       | |        | val2.2 | second line has 1st empty cell. | | val3.1 | third  | line has 2nd empty cell.        | | val4.1 | val4.2 | fourth line has all values.     | | val5.1 | fifth  | line has 2nd empty cell.        | +--------+--------+---------------------------------+ XYZ system takes pair(test_data_2, user_snippet2) and expect to produce below result +--------+-----+---------------------------------+ | a      | b   | comment                         | +--------+-----+---------------------------------+ | val1.1 | 1.2 | first_line_has_all_values       | |        | 2.2 | second line has 1st empty cell. | | val3.1 |     | third line has 2nd empty cell.  | | val4.1 | 4.2 | fourth line has all values.     | | val5.1 |     | fifth line has 2nd empty cell.  | +--------+-----+---------------------------------+ good news is that XYZ system produces the expected result.                
Tommy's Work ... Step #3: Creating Statement Problem ----------    Problem Statement 1:      Find a root cause that        + the good result was produced from pair(test_data_2, user_snippet_2),        + but the bad result was produced from pair(test_data_1, user_snippet_1).        Problem Statement 2:      Find a root cause that the incorrect result is produced from pair(test_data_1, user_snippet_1).                
Tommy's Work ... Step #4: Creating Cheat Sheet To Explain Technical Terms ---------- Keywords            Description ------------------- -------------------------------------------------- start               telling system to search at begining. end                 telling system to stop searching. mixed_word          telling system to mach at least one alphabet number character,                    other characters are printable characters such as letters, digits, or punctuations.                    For example, "a", "1.2.3.4", "(a)" are mixed-word.                                 ++++++++++                                 "-", "+++---" are NOT mixed-word because they don't                                 contain at least one alphabet number character. mixed_words         telling system to match at least one mix-word and separator between                    mixed-word is a blank space character.                    In other words, mixed-words is a group of mixed-word separating by a blank space.                    For example, "a", "1.2.3.4", "yellow pencil." are mixed-words.                                 ++++++++++                                 "yellow   pencil." are NOT mixed-words because                                    separator requirement of mix-words is a blank space character (i.e., ).                                    In this case, separator between "yellow" and "pencil."                                    triple-blank-space (i.e.,   ) which is not met mix-words separator requirement.                                 "yellow - pencil." are NOT mixed-words because                                    second word (that is hyphen character or "-")                                    doesn't contain at least one alphabet number character. number              telling system to match digits or floating point.                    For example, "1", "123', "123.456" are number. space               telling system to match a blank space character. spaces              telling system to match at least one blank space. Arguments           Description ------------------- -------------------------------------------------- var_<name>          telling system to store a matching to variable <name> repetition_<m>_<n>  telling system to repeat matching from <m> to <n> times.                
Lucy's Work ... Step #1: Simplifying User Snippet For Better Recognition After discussing with Tomy, Lucy organizes user snippet for more human readable such as, user_snippet_1 ---------- start() mixed_word(var_a)      spaces()   mixed_word(var_b)       spaces()  mixed_words(var_comment) end() start() mixed_word(var_a)      space()    space(repetition_8_11)  space()   mixed_words(var_comment) end() start() space(repetition_7_9)  space()    mixed_word(var_b)       spaces()  mixed_words(var_comment) end() user_snippet_2 ---------- start() mixed_word(var_a)      spaces()   number(var_b)           spaces()  mixed_words(var_comment) end() start() mixed_word(var_a)      space()    space(repetition_8_11)  space()   mixed_words(var_comment) end() start() space(repetition_7_9)  space()    number(var_b)           spaces()  mixed_words(var_comment) end()                
Lucy's Work ... Step #2: Customizing Test Data For Better Investigating Problem Since problem happened at line #3 and #5, only use those data for investigation. test_data_1 a        b       comment -------- ------- -------------------- ... val3.1           third line has 2nd empty cell. ... val5.1           fifth line has 2nd empty cell. test_data_2 a        b       comment -------- ------- -------------------- ... val3.1           third line has 2nd empty cell. ... val5.1           fifth line has 2nd empty cell.                
Lucy collaborates with Tomy to find a root cause ... Step #3: Reasoning to Find a Root Cause for Problem Statement 1 Since pair(test_data_2, user_snippet_2 produces the correct result, Lucy might begin with this pair because its result might be a baseline result for incorrect result.  Assuming Lucy analyzes this pair.
test_data_2 a        b       comment -------- ------- -------------------- ... val3.1           third line has 2nd empty cell. ... val5.1           fifth line has 2nd empty cell. user_snippet_2 ---------- start() mixed_word(var_a)      spaces()   number(var_b)           spaces()  mixed_words(var_comment) end() start() mixed_word(var_a)      space()    space(repetition_8_11)  space()   mixed_words(var_comment) end() start() space(repetition_7_9)  space()    number(var_b)           spaces()  mixed_words(var_comment) end()                
Lucy asks:    Tomy, can you explain how to begin? Tomy replies:    XYZ system will pick the first line of user_snippet_2 and then process.    start() mixed_word(var_a)      spaces()   number(var_b)           spaces()  mixed_words(var_comment) end() Lucy observes and then asks:    start() mixed_word(var_a)    It seems XYZ system will look for mixed-word at beginining.    Is "val3.1" a mixed-word? Tomy replies:    Yes, it is. Lucy observes and then asks:    ... mixed_word(var_a)      spaces()    Is there at least one blank space after "val3.1" Tomy replies:    Yes, it is.  It should have at least 1 blank space and at most 8 blank spaces after "val3.1". Lucy observes and then asks:    ... spaces()   number(var_b)    Is there any number after "val3.1" and multiple blank spaces? Tomy replies:    No, "third" is a word.  It can't be number. Lucy asks:    It is a failed case.  What next? Tomy replies:    XYZ system will pick the next line of user_snippet_2 (i.e., second line) and then process.    start() mixed_word(var_a)      space()    space(repetition_8_11)  space()   mixed_words(var_comment) end() Lucy asks:    start() mixed_word(var_a)    Is "val3.1" a mixed-word? Tomy replies:    Yes. Lucy asks:    ... mixed_word(var_a)      space()    space(repetition_8_11)  space()    Calculating: 1-space + range(8, 11)-space + 1-space                 should be equal to range(10, 13)-space                 or should be equal to at least 10-space or at most 13-space    Is there around 10 to 13 blank spaces after "val3.1"? Tomy replies:    Yes. Lucy asks:    ... space()    space(repetition_8_11)  space()   mixed_words(var_comment)    Is there mixed-words after 10 to 13 blank spaces? Tomy replies:    Yes, "third line has 2nd empty cell." is mixed-words. ... ... conversation is continued ...                
At the end, both Tomy and Lucy know that the second line of user_snippet_2 should parse below data correctly. That is ... start() mixed_word(var_a)      space()    space(repetition_8_11)  space()   mixed_words(var_comment) end() a        b       comment -------- ------- -------------------- ... val3.1           third line has 2nd empty cell. ... val5.1           fifth line has 2nd empty cell.
test_data_1 a        b       comment -------- ------- -------------------- ... val3.1           third line has 2nd empty cell. ... val5.1           fifth line has 2nd empty cell. user_snippet_1 ---------- start() mixed_word(var_a)      spaces()   mixed_word(var_b)       spaces()  mixed_words(var_comment) end() start() mixed_word(var_a)      space()    space(repetition_8_11)  space()   mixed_words(var_comment) end() start() space(repetition_7_9)  space()    mixed_word(var_b)       spaces()  mixed_words(var_comment) end()                
Next, Lucy and Tomy continue to find root cause of on pair(test_data_1, user_snippet_1) where the first line of user_snippet_1 is start() mixed_word(var_a)      spaces()   mixed_word(var_b)       spaces()  mixed_words(var_comment) end()                
Lucy asks:    start() mixed_word(var_a)    Is "val3.1" a mixed-word? Tomy replies:    Yes. Lucy asks:    ... mixed_word(var_a)      spaces()    Is there at least one blank space after "val3.1" Tomy replies:    Yes. Lucy asks:    ... spaces()   mixed_word(var_b)    Is there a mixed-word after "val3.1" and multiple blank spaces? Tomy replies:    Yes, there is a word after "val3.1" and multiple blank spaces.  That word is "third".    But NO, because "third" word supposes to be a first word in "comment" column.    Now, it incorrectly assigns to "b" column. Lucy explains:    Based on the sequence of        start mixed-word   spaces               mixed-word   spaces              mixed-words end      where spaces is interpreted as one-or-more-spaces (i.e., at least one blank space)      can be rewritten as    start mixed-word   one-or-more-spaces   mixed-word   one-or-more-spaces   mixed-words end        Therefore, the first snippet line of user_snippet_1 should be the following data for test_data_1    start mixed-word   one-or-more-spaces   mixed-word   one-or-more-spaces   mixed-words end          "val1.1"     "   "                "val1.2"     "  "                 "first_line_has_all_values"          "val3.1"     "           "        "third"      " "                  "line has 2nd empty cell."          "val4.1"     "   "                "val4.2"     "  "                 "fourth line has all values."          "val5.1"     "           "        "fifth"      " "                  "line has 2nd empty cell." Lucy continues:    Let's talk about pair(test_data_2, user_snippet_2).        Based testing result, the pair(test_data_2, user_snippet_2) is produced correct result.        Actually, this pair(test_data_2, user_snippet_2) is RANDOMLY produced correct result    because all first word in "comment" column is a word or mixed-word which is distinguished with number.        However, if a first word in "comment" column becomes number,    then pair(test_data_2_suggested_by_Lucy, user_snippet_2) should produce incorrect result.            test_data_2_suggested_by_Lucy        a        b       comment        -------- ------- --------------------        val1.1   1.2     first_line_has_all_values                 2.2     second line has 1st empty cell.        val3.1           3 rd line has 2nd empty cell.        val4.1   4.2     fourth line has all values.        val5.1           5 th line has 2nd empty cell.                user_snippet_2        ----------            a        b       comment        start() mixed_word(var_a)  number(var_b)  mixed_words(var_comment) end() -> record        start() mixed_word(var_a) space(repetition_8_11) mixed_words(var_comment) end() -> record        start() space(repetition_7_9) number(var_b)  mixed_words(var_comment) end() -> record                Test Result        ----------        +--------+-----+---------------------------------+        | a      | b   | comment                         |        +--------+-----+---------------------------------+        | val1.1 | 1.2 | first_line_has_all_values       |        |        | 2.2 | second line has 1st empty cell. |        | val3.1 | 3   | rd line has 2nd empty cell.     |        | val4.1 | 4.2 | fourth line has all values.     |        | val5.1 | 5   | th line has 2nd empty cell.     |        +--------+-----+---------------------------------+                
Tomy performs hand-on test to confirm Lucy's finding:    >>>    >>> # start() mixed_word(var_a)  number(var_b)  mixed_words(var_comment) end()    >>> #   rewritten to name mnemonics or chunking style    >>> # start()mixed_word(var_a)spaces()number(var_b)spaces()mixed_words(var_comment)end()    >>> #   rewritten to human readable language    >>> # {start}{mixed_word_a}{one_or_more_spaces}{number_b}{one_or_more_spaces}{mixed_words_comment}{end}    >>> pattern_format = "{start}{mixed_word_a}{one_or_more_spaces}{number_b}{one_or_more_spaces}{mixed_words_comment}{end}"    >>>    >>> start = "^"    >>> end = "$"    >>> mixed_word = "[\\x21-\\x7e]*[a-zA-Z0-9][\\x21-\\x7e]*"    >>> number = "\\d*[.]?\\d+"    >>> one_or_more_spaces = " +"    >>>    >>> mixed_word_a = "(?P<a>{mixed_word})".format(mixed_word=mixed_word)    >>> number_b = "(?P<b>{number})".format(number=number)    >>> mixed_words_comment = "(?P<comment>{mixed_word}( {mixed_word})*)".format(mixed_word=mixed_word)    >>>    >>> pattern = pattern_format.format(    ...   start=start,    ...   end=end,    ...   mixed_word_a=mixed_word_a,    ...   mixed_words_comment=mixed_words_comment,    ...   number_b=number_b,    ...   one_or_more_spaces=one_or_more_spaces    ... )    >>>    >>> print(pattern)    ^(?P<a>[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) +(?P<b>\d*[.]?\d+) +(?P<comment>[\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*)$    >>>    >>> import re    >>>    >>> # expecting pattern won't match test data.    >>> test_data = "val3.1           third line has 2nd empty cell."    >>> result = re.search(pattern, test_data)    >>> print(result)    None    >>>    >>> # expecting pattern matches test_data_suggested_by_lucy    >>> test_data_suggested_by_lucy = "val3.1           3 rd line has 2nd empty cell."    >>> result = re.search(pattern, test_data_suggested_by_lucy)    >>> print(result)    <re.Match object; span=(0, 46), match='val3.1           3 rd line has 2nd empty cell.'>    >>> print(result.groupdict())    {'a': 'val3.1', 'b': '3', 'comment': 'rd line has 2nd empty cell.'}    >>>                
After tested Lucy's finding, Tomy agree with Lucy that    pair(test_data_2, user_snippet_2) is NOT ALWAYS produced correct result    because a first word of "comment" column can be anything such as:        letter(s), number, word, mixed-word, punctuation(s), etc.,.                
Lucy continues to investigate second problem ... Step #4: Reasoning to Find a Root Cause for Problem Statement 2 Since problem happened at first line of either user_snippet_1 or user_snippeet_2, let's focus on a first line snippet start() mixed_word(var_a)      spaces()   mixed_word(var_b)       spaces()  mixed_words(var_comment) end() ... start() mixed_word(var_a)      spaces()   number(var_b)           spaces()  mixed_words(var_comment) end()
Lucy analyses blank-spaces definition:    spaces is interpreted as matching at least one blank space, or                             matching one or more spaces.    In logic thinking,    "at least one ..." or "one or more ... "        implies    "as many as possible", or "infinite quantities"     Lucy adds notes:    start() mixed_word(var_a)      spaces(<infinite quantities>)   mixed_word(var_b)       spaces(<infinite quantities>)  mixed_words(var_comment) end()    ...    start() mixed_word(var_a)      spaces(<infinite quantities>)   number(var_b)           spaces(<infinite quantities>)  mixed_words(var_comment) end()                
Lucy asks:    Is there infinite blank spaces between "a" and "b" columns?    Is there infinite blank spaces between "b" and "comment" columns? Tomy responses:    No, it should be at least one blank space and at most eight blank spaces between "a" and "b" columns.    And, it should be at least one blank space and at most eight blank spaces between "b" and "comment" columns. Lucy explains:    Actual structure (or, skeleton) of content have finitie blank spaces quantities, but some of them    have used infinite blank spaces quantities which cause to transform current structure    to different structure.    The problem should be corrected if finite quantities apply accordingly.                
Tomy work ... Step #5: Correcting Problem for user_snippet_1 and test_data_1 User Snippet ---------- a        b       comment start() mixed_word(var_a) space(repetition__7)mixed_word(var_b) space(repetition__7)mixed_words(var_comment) end() -> record start() mixed_word(var_a) space(repetition_8_11) mixed_words(var_comment) end() -> record start() space(repetition_7_9) mixed_word(var_b) space(repetition__7)mixed_words(var_comment) end() -> record Generated TextFSM template ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-27 16:49 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value comment ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Start  ^a +b +comment  ^${a}  {,7}${b}  {,7}${comment}$$ -> Record  ^${a}  {8,11} ${comment}$$ -> Record  ^ {7,9} ${b}  {,7}${comment}$$ -> Record Test Data ----------   a        b       comment -------- ------- -------------------- val1.1   val1.2  first_line_has_all_values         val2.2  second line has 1st empty cell. val3.1           third line has 2nd empty cell. val4.1   val4.2  fourth line has all values. val5.1           fifth line has 2nd empty cell. Test Result ---------- Total-rows-count: 5 [{'a': 'val1.1', 'b': 'val1.2', 'comment': 'first_line_has_all_values'}, {'a': '', 'b': 'val2.2', 'comment': 'second line has 1st empty cell.'}, {'a': 'val3.1', 'b': '', 'comment': 'third line has 2nd empty cell.'}, {'a': 'val4.1', 'b': 'val4.2', 'comment': 'fourth line has all values.'}, {'a': 'val5.1', 'b': '', 'comment': 'fifth line has 2nd empty cell.'}] or Total-rows-count: 5 +--------+--------+---------------------------------+ | a      | b      | comment                         | +--------+--------+---------------------------------+ | val1.1 | val1.2 | first_line_has_all_values       | |        | val2.2 | second line has 1st empty cell. | | val3.1 |        | third line has 2nd empty cell.  | | val4.1 | val4.2 | fourth line has all values.     | | val5.1 |        | fifth line has 2nd empty cell.  | +--------+--------+---------------------------------+                
Tomy work ... Step #6: Correcting Problem for user_snippet_2 and test_data_2 User Snippet ---------- a        b       comment start() mixed_word(var_a) space(repetition__7)number(var_b) space(repetition__7)mixed_words(var_comment) end() -> record start() mixed_word(var_a) space(repetition_8_11) mixed_words(var_comment) end() -> record start() space(repetition_7_9) number(var_b) space(repetition__7)mixed_words(var_comment) end() -> record Generated TextFSM template ---------- # Non-commercial use: generated by TemplateApp v.0.1.13 (geekstrident.com) # Created Date: 2023-09-27 16:56 ################################ Value a ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*) Value b (\d*[.]?\d+) Value comment ([\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*( [\x21-\x7e]*[a-zA-Z0-9][\x21-\x7e]*)*) Start  ^a +b +comment  ^${a}  {,7}${b}  {,7}${comment}$$ -> Record  ^${a}  {8,11} ${comment}$$ -> Record  ^ {7,9} ${b}  {,7}${comment}$$ -> Record Test Data ----------   a        b       comment -------- ------- -------------------- val1.1   val1.2  first_line_has_all_values         val2.2  second line has 1st empty cell. val3.1           third line has 2nd empty cell. val4.1   val4.2  fourth line has all values. val5.1           fifth line has 2nd empty cell. Test Result ---------- Total-rows-count: 5 [{'a': 'val1.1', 'b': '1.2', 'comment': 'first_line_has_all_values'}, {'a': '', 'b': '2.2', 'comment': 'second line has 1st empty cell.'}, {'a': 'val3.1', 'b': '', 'comment': 'third line has 2nd empty cell.'}, {'a': 'val4.1', 'b': '4.2', 'comment': 'fourth line has all values.'}, {'a': 'val5.1', 'b': '', 'comment': 'fifth line has 2nd empty cell.'}] or Total-rows-count: 5 +--------+-----+---------------------------------+ | a      | b   | comment                         | +--------+-----+---------------------------------+ | val1.1 | 1.2 | first_line_has_all_values       | |        | 2.2 | second line has 1st empty cell. | | val3.1 |     | third line has 2nd empty cell.  | | val4.1 | 4.2 | fourth line has all values.     | | val5.1 |     | fifth line has 2nd empty cell.  | +--------+-----+---------------------------------+                
Tomy work ... Step #7: Preparing Written Report for Team During creating report for management team, Tomy might emphasize some advantages to boost train processes such as  - Leveraging the advantage of end-user group that all members are surplus knowlege of test data.  - End-users can build or describe plain text snippet to solve problem.  - When end-users tackle a problem, end-users can collaborate with other end-users to solve problem.                

The main purpose of TemplateApp application is to simplify a process of building TextFSM template to improve code quality.  Most features are designed to solve specific problem relating to human error, wasting resources, or complex training.  Experience individuals with solid knowledge of building TextFSM template who rely too much on TemplateApp MUST BE a result of lowering design or creating TextFSM template over time.

There are three types of keywords: , , and .   These keywords associate with to enhance matching or capturing capacity.

Reserved Keywords

There are four reversed keywords: , , , and .
start
an indicator keyword to generate regex pattern to match at the beginning of string.  Its keyword arguments are: space, spaces, ws, whitespace, or whitespaces.

Usage
start() or start(ws) start(space) or start(spaces) start(whitespace) or start(whitespaces)

Example
start() should generate "^"
start(space) should generate "^ *"
start(spaces) should generate "^ +"
start(ws) should generate r"^\s*"
start(whitespace) should generate r"^\s*"
start(whitespaces) should generate r"^\s+"
end
an indicator keyword to generate regex pattern to match at the end of string.  Its keyword arguments are: space, spaces, ws, whitespace, or whitespaces.

Usage
end() or end(ws) end(space) or end(spaces) end(whitespace) or end(whitespaces)

Example
end() should generate "$"
end(space) should generate " *$"
end(spaces) should generate " +$"
end(ws) should generate r"\s*$"
end(whitespace) should generate r"\s*$"
end(whitespaces) should generate r"\s+$"
data
an matching or capturing keyword to generate regex pattern to match or to capture raw data.  Its keyword arguments should be similar to system keyword arguments.

Usage
data(<raw_data>) data(var_<name>, <raw_data>, <other_arguments>)

Example
data(..) data(var_v1, ++>passed, word_bound_right) should generate r"\.\. (?P<v1>(\+\+>passed)\b)"
>>> >>> import re >>> test_data = ".. ++>passed" >>> # assuming generated pattern is >>> pattern = r"\.\. (?P<v1>(\+\+>passed)" >>> match = re.search(pattern, test_data) >>> print(match) <re.Match object; span=(0, 12), match='.. ++>passed'> >>> print(match.groupdict()) {'v1': '++>passed'} >>>
end
an matching or capturing keyword to generate regex pattern to match or to capture predefined symbols.  Its keyword arguments should be similar to system keyword arguments.

Usage
symbol(name=<symbol_name>) symbol(var_<name>, name=<symbol_name>, <other_arguments>)

Example
start() symbol(name=dot, 5_occurrences)  symbol(name=hyphen, at_least_3_occurrence) end() should generate r"^\.{5} +-{3,}$"
The generated pattern should match this ".....   ----------" data.
The generated pattern should not match this "........   ----------" data.

System Keywords

These keywords are created by App owner or                integration partner and stored in system_references.yaml and it can be found in package installation directory.  Keywords MUST NOT DUPLICATE reserved keywords.  System keywords MUST BE a file based because Python package installation process SHOULD make it to become READONLY keywords (i.e., unmodified keywords).  Checkout these examples: , , , , , .
Usage
<keyword>(var_<name>, <other_data,...>, or_empty, or_<other_keywords>, <keyword_arguments>)
Example #1
digits() is a format of
<keyword>()
which should match at least one numeric.
Its generated pattern is r"\d+".
Example #2
digits(N/A) is a format of
<keyword>(<other_data,...>)
which should match at least one numeric or N/A data.
Its generated pattern is r"\d+|N/A".
Example #3
digits(var_v1, N/A) is a format of
<keyword>(var_<name>, <other_data,...>)
which should capture at least one numeric or N/A data, and then store in v1 variable.
Its generated pattern is r"(?P<v1>\d+|N/A)".
Example #4
digits(var_v1, or_empty) is a format of
<keyword>(var_<name>, or_empty)
which should capture at least one numeric or empty data and then store in v1 variable.
Its generated pattern is r"(?P<v1>\d+|)".
>>> import re >>> >>> # start() Food: word(var_food, or_empty)  Total: digits(var_total, N/A) end() >>> # Assuming pattern is generated from above user data snippet >>> pattern = r"^Food:\s*(?P<food>[a-zA-Z][a-zA-Z0-9]*|) +Total: (?P<total>\d+|N/A)$" >>> >>> test_data = """ ... Food: Mango    Total: 159 ... Food:          Total: N/A ... """.strip() >>> >>> for line in test_data.splitlines(): ...   match = re.search(pattern, line) ...   if match: ...     print(match.groupdict()) ... {'food': 'Mango', 'total': '159'} {'food': '', 'total': 'N/A'} >>>
Example #5
ipv4_addr(var_addr, or_mac_addr) is a format of
<keyword>(var_<name>, or_<other_keyword>)
which should capture either IPv4 address or MAC address, and then store in addr variable.
Its generated pattern is r"^Address: (?P<addr>\d{1,3}([.]\d{1,3}){3}|([0-9a-fA-F]{2}([: -][0-9a-fA-F]{2}){5})) *$".
>>> # start() Address: ipv4_addr(var_addr, or_mac_addr) end(space) >>> # Assuming pattern is generated from above user data snippet >>> pattern = r"^Address: (?P<addr>\d{1,3}([.]\d{1,3}){3}|([0-9a-fA-F]{2}([: -][0-9a-fA-F]{2}){5})) *$" >>> >>> test_data = """ ... Address: 192.168.1.1 ... Address: AA:11:BB:22:CC:33 ... """.strip() >>> >>> import re >>> >>> for line in test_data.splitlines(): ...   match = re.search(pattern, line) ...   if match: ...     print(match.groupdict()) ... {'addr': '192.168.1.1'} {'addr': 'AA:11:BB:22:CC:33'} >>>
Example #6
letters(var_v1, 5_occurrence) is a format of
<keyword>(var_<name>, <keyword_arguments>)
which should match at least one numeric.
Its generated pattern is r"(?P<v1>[a-zA-Z]{5})".

User Keywords

These keywords are created by users and stored in ~/.geekstrident/regexpro/user_references.yaml file on deployed system.  Keywords MUST NOT DUPLICATE reserved and system keywords.  Future version should provide an option to let users store or access user keyword by using database.  The usage of user keywords is similar to the usage of system keywords.

Keyword Arguments

Categories: , , , , , , , , and .

var_<name>
encapsulate keyword pattern with (?P<name>...).  It should let regex store matching data to variable.
word(var_v1) should generate r"(?P<v1>[a-zA-Z][a-zA-Z0-9]*)" and store matching word to v1 variable.
or_empty
join keyword pattern and empty string with | separator, and then encapsulate result with parenthesis, i.e., (...|).   It should let regex match zero or pattern.
word(or_empty) should generate r"([a-zA-Z][a-zA-Z0-9]*|)" that should match a word or an empty string.

or_<other_keyword>
join keyword pattern and other-keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (...|...).   It should let regex match either keyword pattern or other keyword pattern.
time(or_number, format4) should generate r"(\d{2}:\d{2})|((\d+)?[.]?\d+)" that should match a number or time value of format 4 HH:MM.

or_<datum>
<datum>
join keyword pattern and datum with | separator.
Case 1: no enclosed parenthesis if data is singular form, i.e.,...|data1|data2|data_n.
digits(or_n/a, or_null, or_none) should generate r"(\d+)|n/a|null|none".
digits(n/a, null, none) should generate r"(\d+)|n/a|null|none".
Case 2: with enclosed parenthesis if data is plural form, i.e., (...|(data 1)|data2|data_n).
digits(or_n/a, or_not applicable) should generate r"((\d+)|n/a|(not applicable))".
digits(n/a, not applicable) should generate r"((\d+)|n/a|(not applicable))".

or_repeat_k_space
or_k_space
join ( {k}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {k})|...).
word(var_v1, or_repeat_5_space) should generate r"(?P<v1>( {5})|([a-zA-Z][a-zA-Z0-9]*))".
word(var_v1, or_5_space) should generate r"(?P<v1>( {5})|([a-zA-Z][a-zA-Z0-9]*))".

or_repeat_m_n_space
join ( {m,n}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {m,n})|...).
word(var_v1, or_repeat_2_5_space) should generate r"(?P<v1>( {2,5})|([a-zA-Z][a-zA-Z0-9]*))".

or_repeat__n_space
or_at_most_n_space
join ( {,n}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {,n})|...).
word(var_v1, or_repeat__5_space) should generate r"(?P<v1>( {,5})|([a-zA-Z][a-zA-Z0-9]*))".
word(var_v1, or_at_most_5_space) should generate r"(?P<v1>( {,5})|([a-zA-Z][a-zA-Z0-9]*))".

or_repeat_m__space
or_at_least_m_space
join ( {m,}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {m,})|...).
word(var_v1, or_repeat_2__space) should generate r"(?P<v1>( {2,})|([a-zA-Z][a-zA-Z0-9]*))".
word(var_v1, or_at_least_5_space) should generate r"(?P<v1>( {2,})|([a-zA-Z][a-zA-Z0-9]*))".

or_either_repeat_k_space
or_either_k_space
join ( {k}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {k})| *... *).
word(var_v1, or_either_repeat_5_space) should generate r"(?P<v1>( {5})|( *[a-zA-Z][a-zA-Z0-9]* *))".
word(var_v1, or_either_5_space) should generate r"(?P<v1>( {5})|( *[a-zA-Z][a-zA-Z0-9]* *))".

or_either_repeat_m_n_space
join ( {m,n}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {m,n})| *... *).
word(var_v1, or_either_repeat_2_5_space) should generate r"(?P<v1>( {2,5})|( *[a-zA-Z][a-zA-Z0-9]* *))".

or_either_repeat__n_space
or_either_at_most_n_space
join ( {,n}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {,n})| *... *).
word(var_v1, or_either_repeat__5_space) should generate r"(?P<v1>( {,5})|( *[a-zA-Z][a-zA-Z0-9]* *))".
word(var_v1, or_either_at_most_5_space) should generate r"(?P<v1>( {,5})|( *[a-zA-Z][a-zA-Z0-9]* *))".

or_either_repeat_m__space
or_either_at_least_m_space
join ( {m,}) and keyword pattern with | separator, and then encapsulate result with parenthesis, i.e., (( {m,})| *... *).
word(var_v1, or_either_repeat_2__space) should generate r"(?P<v1>( {2,})|( *[a-zA-Z][a-zA-Z0-9]* *))".
word(var_v1, or_either_at_least_5_space) should generate r"(?P<v1>( {2,})|( *[a-zA-Z][a-zA-Z0-9]* *))".
repeat_k
append {k} to keyword pattern which transform to match exact k-time.
letter(var_v1, repetition_5) should generate r"(?P<v1>[a-zA-Z]{5})".

repeat_m _n
append {m,n} to keyword pattern which transform to match at least m-time and at most n-time.
letter(var_v1, repetition_2_5) should generate r"(?P<v1>[a-zA-Z]{2,5})".

repeat__n
append {,n} to keyword pattern which transform to match at most n-time.
letter(var_v1, repetition__5) should generate r"(?P<v1>[a-zA-Z]{,5})".

repeat_m_
append {m,} to keyword pattern which transform to match at least m-time.
letter(var_v1, repetition_2_) should generate r"(?P<v1>[a-zA-Z]{2,})".
k_occurrence
if k = 0, append ? to keyword pattern which transform to match zero or one matching.
if k = 1, is to match itself.
if k > 1, append {k} to keyword pattern which transform to match k-times.
letter(var_v1, 0_occurrence) should generate r"(?P<v1>[a-zA-Z]?)".
letter(var_v1, 1_occurrence) should generate r"(?P<v1>[a-zA-Z])".
letter(var_v1, 5_occurrence) should generate r"(?P<v1>[a-zA-Z]{5})".

0_or_1_occurrence
append ? to keyword pattern which transform to match zero or one.
letter(var_v1, 0_or_1_occurrence) should generate r"(?P<v1>[a-zA-Z]?)".

k_or_more_occurrence
if k = 0, append * to keyword pattern which transform to match zero or more matching.
if k = 1, append + to keyword pattern which transform to match at least one matching.
if k > 1, append {k,} to keyword pattern which transform to match at least k-times.
letter(var_v1, 0_or_more_occurrence) should generate r"(?P<v1>[a-zA-Z]*)".
letter(var_v1, 1_or_more_occurrence) should generate r"(?P<v1>[a-zA-Z]+)".
letter(var_v1, 3_or_more_occurrence) should generate r"(?P<v1>[a-zA-Z]{3,})".

at_least_m_occurrence
if m = 0, append * to keyword pattern which transform to match zero or more matching.
if m >= 1, append {m,} to keyword pattern which transform to match at least m-times.
letter(var_v1, at_least_0_occurrence) should generate r"(?P<v1>[a-zA-Z]*)".
letter(var_v1, at_least_3_occurrence) should generate r"(?P<v1>[a-zA-Z]{3,})".

at_most_n_occurrence
if n = 0, append ? to keyword pattern which transform to match zero or one matching.
if n >= 1, append {,n} to keyword pattern which transform to match at most n-times.
letter(var_v1, at_most_0_occurrence) should generate r"(?P<v1>[a-zA-Z]?)".
letter(var_v1, at_most_8_occurrence) should generate r"(?P<v1>[a-zA-Z]{,8})".
k_phrase_occurrence
k_group_occurrence
if k = 0, append ( ...)? or ( +...)? to keyword pattern which transform to match one or two matching.
if k = 1, append ( ...) or ( +...) to keyword pattern which transform to match exact two matching.
if k > 1, append ( ...){k,} or ( +...){k,} to keyword pattern which transform to match exact (k + 1) matching.
word(0_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)?".
word(0_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)?".
word(1_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)".
word(1_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)".
word(5_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*){5}".
word(5_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*){5}".

0_or_1_phrase_occurrence
0_or_1_group_occurrence
append ( ...)? or ( +...)? to keyword pattern which transform to match one or two matching.
word(0_or_1_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)?".
word(0_or_1_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)?".

k_or_more_phrase_occurrence
k_or_more_group_occurrence
if k = 0, append ( ...)* or ( +...)* to keyword pattern which transform to match at least one matching.
if k = 1, append ( ...)+ or ( +...)+ to keyword pattern which transform to match at least two matching.
if k > 1, append ( ...){k,} or ( +...){k,} to keyword pattern which transform to match at least (k + 1) matching.
word(0_or_more_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)*".
word(0_or_more_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)*".
word(1_or_more_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)+".
word(1_or_more_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)+".
word(5_or_more_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*){5,}".
word(5_or_more_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*){5,}".

at_least_m_phrase_occurrence
at_least_m_group_occurrence
if m = 0, append ( ...)* or ( +...)* to keyword pattern which transform to match at least one matching.
if m >= 1, append ( ...){m,} or ( +...){m,} to keyword pattern which transform to match at least (m + 1) matching.
word(at_least_0_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)*".
word(at_least_0_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)*".
word(at_least_5_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*){5,}".
word(at_least_5_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*){5,}".

at_most_n_phrase_occurrence
at_most_n_group_occurrence
if n = 0, append ( ...)? or ( +...)? to keyword pattern which transform to match at most one or two matching.
if n >= 1, append ( ...){,n} or ( +...){,n} to keyword pattern which transform to match at most (n + 1) matching.
word(at_most_0_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*)?".
word(at_most_0_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*)?".
word(at_most_5_phrase_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( [a-zA-Z][a-zA-Z0-9]*){,5}".
word(at_most_5_group_occurrence) should generate r"[a-zA-Z][a-zA-Z0-9]*( +[a-zA-Z][a-zA-Z0-9]*){,5}".
word_bound_left
prepend \b to the beginning of keyword pattern.  It should let regex perform word bound on left matching.
word(word_bound_left) should generate r"\b[a-zA-Z][a-zA-Z0-9]*".

word_bound_right
append \b to the end of keyword pattern.  It should let regex perform word bound on right matching.
word(word_bound_right) should generate r"[a-zA-Z][a-zA-Z0-9]*\b".

word_bound
enclose \b around keyword pattern.  It should let regex perform word bound on matching.
word(word_bound) should generate r"\b[a-zA-Z][a-zA-Z0-9]*\b".
head
prepend ^ to keyword pattern which inform regex engine that pattern should start at the beginning of string.
word(head) should generate r"^[a-zA-Z][a-zA-Z0-9]*".

head_ws
head_whitespace
prepend ^\\s* to keyword pattern which inform regex engine that pattern should start at the beginning of string with zero or some whitespaces.
word(head_whitespace) should generate r"^\s*[a-zA-Z][a-zA-Z0-9]*".

head_ws_plus
head_whitespaces
head_whitespace_plus
prepend ^\\s+ to keyword pattern which inform regex engine that pattern should start at the beginning of string with some whitespaces.
word(head_whitespaces) should generate r"^\s+[a-zA-Z][a-zA-Z0-9]*".

head_space
prepend ^ * to keyword pattern which inform regex engine that pattern should start at the beginning of string with zero or some blank-space.
word(head_space) should generate r"^ *[a-zA-Z][a-zA-Z0-9]*".

head_spaces
head_space_plus
prepend ^ + to keyword pattern which inform regex engine that pattern should start at the beginning of string with some blank-space.
word(head_spaces) should generate r"^ +[a-zA-Z][a-zA-Z0-9]*".

head_just_ws
head_just_whitespace
prepend \\s* to keyword pattern which transform to match zero or some whitespaces and then pattern.
word(head_just_whitespace) should generate r"\s*[a-zA-Z][a-zA-Z0-9]*".

head_just_ws_plus
head_just_whitespaces
head_just_whitespace_plus
prepend \\s+ to keyword pattern which transform to match some whitespaces and then pattern.
word(head_just_whitespaces) should generate r"\s+[a-zA-Z][a-zA-Z0-9]*".

head_just_space
prepend * to keyword pattern which transform to match zero or some blank-space and then pattern.
word(head_just_space) should generate r" *[a-zA-Z][a-zA-Z0-9]*".

head_just_spaces
head_just_space_plus
prepend + to keyword pattern which transform to match some blank-space and then pattern.
word(head_just_spaces) should generate r" +[a-zA-Z][a-zA-Z0-9]*".
tail
append $ to keyword pattern which inform regex engine that stop matching after seeing pattern.
word(tail) should generate r"[a-zA-Z][a-zA-Z0-9]*$".

tail_ws
tail_whitespace
append \\s*$ to keyword pattern which inform regex engine that stop matching after seeing pattern and zero or some whitespaces.
word(tail_whitespace) should generate r"[a-zA-Z][a-zA-Z0-9]*\s*$".

tail_ws_plus
tail_whitespaces
tail_whitespace_plus
prepend \\s+$ to keyword pattern which inform regex engine that stop matching after seeing pattern and some whitespaces.
word(tail_whitespaces) should generate r"[a-zA-Z][a-zA-Z0-9]*\s+$".

tail_space
prepend *$ to keyword pattern which inform regex engine that stop matching after seeing pattern and zero or some blank-space.
word(tail_space) should generate r"[a-zA-Z][a-zA-Z0-9]* *$".

tail_spaces
tail_space_plus
append +$ to keyword pattern which inform regex engine that stop matching after seeing pattern and some blank-space.
word(tail_spaces) should generate r"[a-zA-Z][a-zA-Z0-9]* +$".

tail_just_ws
tail_just_whitespace
append \\s* to keyword pattern which transform to match pattern and then zero or some whitespaces.
word(tail_just_whitespace) should generate r"[a-zA-Z][a-zA-Z0-9]*\s*".

tail_just_ws_plus
tail_just_whitespaces
tail_just_whitespace_plus
append \\s+ to keyword pattern which transform to match pattern and then some whitespaces
word(tail_just_whitespaces) should generate r"[a-zA-Z][a-zA-Z0-9]*\s+".

tail_just_space
append * to keyword pattern which transform to match pattern and zero or some blank-space.
word(tail_just_space) should generate r"[a-zA-Z][a-zA-Z0-9]* *".

tail_just_spaces
tail_just_space_plus
append + to keyword pattern which transform to match pattern and then some blank-space.
word(tail_just_spaces) should generate r"[a-zA-Z][a-zA-Z0-9]* +".
?
To Be Announced
letter(var_v1, ?) should generate r"(?P<v1>[a-zA-Z]?)".

*
To Be Announced
letter(var_v1, *) should generate r"(?P<v1>[a-zA-Z]*)".

+
To Be Announced
letter(var_v1, +) should generate r"(?P<v1>[a-zA-Z]+)".

{k}
To Be Announced
letter(var_v1, {0) should generate r"(?P<v1>[a-zA-Z]?)".
letter(var_v1, {1) should generate r"(?P<v1>[a-zA-Z])".
letter(var_v1, {5) should generate r"(?P<v1>[a-zA-Z]{5})".

{m,n}
To Be Announced
letter(var_v1, {,5) should generate r"(?P<v1>[a-zA-Z]{,5})".
letter(var_v1, {2,) should generate r"(?P<v1>[a-zA-Z]{2,})".
letter(var_v1, {2,5) should generate r"(?P<v1>[a-zA-Z]{2,5})".

phrase?
group?
To Be Announced
letters(var_v1, phrase?) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+)?)".
letters(var_v1, group?) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+)?)".

phrase*
group*
To Be Announced
letters(var_v1, phrase*) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+)*)".
letters(var_v1, group*) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+)*)".

phrase+
group+
To Be Announced
letters(var_v1, phrase+) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+)+)".
letters(var_v1, group+) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+)+)".

phrase{k}
group{k}
To Be Announced
letters(var_v1, phrase{0}) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+)?)".
letters(var_v1, group{0}) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+)?)".
letters(var_v1, phrase{1}) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+))".
letters(var_v1, group{1}) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+))".
letters(var_v1, phrase{5}) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+){5})".
letters(var_v1, group{5}) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+){5})".

phrase{m,n}
group{m,n}
To Be Announced
letters(var_v1, phrase{2,}) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+){2,})".
letters(var_v1, group{2,}) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+){2,})".
letters(var_v1, phrase{,5}) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+){,5})".
letters(var_v1, group{,5}) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+){,5})".
letters(var_v1, phrase{2,5}) should generate r"(?P<v1>[a-zA-Z]+( [a-zA-Z]+){2,5})".
letters(var_v1, group{2,5}) should generate r"(?P<v1>[a-zA-Z]+( +[a-zA-Z]+){2,5})".

or_space?
or_ws?
or_whitespace?
or_either_space?
or_either_ws?
or_either_whitespace?
To Be Announced
letters(var_v1, or_space?) should generate r"(?P<v1>( ?)|([a-zA-Z]+))".
letters(var_v1, or_whitespace?) should generate r"(?P<v1>(\s?)|([a-zA-Z]+))".
letters(var_v1, or_either_space?) should generate r"(?P<v1>( ?)|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace?) should generate r"(?P<v1>(\s?)|( *[a-zA-Z]+ *))".

or_space*
or_ws*
or_whitespace*
or_either_space*
or_either_ws*
or_either_whitespace*
To Be Announced
letters(var_v1, or_space*) should generate r"(?P<v1>( *)|([a-zA-Z]+))".
letters(var_v1, or_whitespace*) should generate r"(?P<v1>(\s*)|([a-zA-Z]+))".
letters(var_v1, or_either_space*) should generate r"(?P<v1>( *)|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace*) should generate r"(?P<v1>(\s*)|( *[a-zA-Z]+ *))".

or_space+
or_ws+
or_whitespace+
or_either_space+
or_either_ws+
or_either_whitespace+
To Be Announced
letters(var_v1, or_space+) should generate r"(?P<v1>( +)|([a-zA-Z]+))".
letters(var_v1, or_whitespace+) should generate r"(?P<v1>(\s+)|([a-zA-Z]+))".
letters(var_v1, or_either_space+) should generate r"(?P<v1>( +)|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace+) should generate r"(?P<v1>(\s+)|( *[a-zA-Z]+ *))".

or_space{k}
or_ws{k}
or_whitespace{k}
or_either_space{k}
or_either_ws{k}
or_either_whitespace{k}
To Be Announced
letters(var_v1, or_space{5}) should generate r"(?P<v1>( {5})|([a-zA-Z]+))".
letters(var_v1, or_whitespace{5}) should generate r"(?P<v1>(\s{5})|([a-zA-Z]+))".
letters(var_v1, or_either_space{5}) should generate r"(?P<v1>( {5})|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace{5}) should generate r"(?P<v1>(\s{5})|( *[a-zA-Z]+ *))".

or_space{m,n}
or_ws{m,n}
or_whitespace{m,n}
or_either_space{m,n}
or_either_ws{m,n}
or_either_whitespace{m,n}
To Be Announced
letters(var_v1, or_space{2,}) should generate r"(?P<v1>( {2,})|([a-zA-Z]+))".
letters(var_v1, or_whitespace{2,}) should generate r"(?P<v1>(\s{2,})|([a-zA-Z]+))".
letters(var_v1, or_either_space{2,}) should generate r"(?P<v1>( {2,})|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace{2,}) should generate r"(?P<v1>(\s{2,})|( *[a-zA-Z]+ *))".
letters(var_v1, or_space{,5}) should generate r"(?P<v1>( {,5})|([a-zA-Z]+))".
letters(var_v1, or_whitespace{,5}) should generate r"(?P<v1>(\s{,5})|([a-zA-Z]+))".
letters(var_v1, or_either_space{,5}) should generate r"(?P<v1>( {,5})|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace{,5}) should generate r"(?P<v1>(\s{,5})|( *[a-zA-Z]+ *))".
letters(var_v1, or_space{2,5}) should generate r"(?P<v1>( {2,5})|([a-zA-Z]+))".
letters(var_v1, or_whitespace{2,5}) should generate r"(?P<v1>(\s{2,5})|([a-zA-Z]+))".
letters(var_v1, or_either_space{2,5}) should generate r"(?P<v1>( {2,5})|( *[a-zA-Z]+ *))".
letters(var_v1, or_either_whitespace{2,5}) should generate r"(?P<v1>(\s{2,5})|( *[a-zA-Z]+ *))".

RegexApp Workflow

Restriction