SAP---ABAP

Sunday, July 8, 2007



ABAP Clipboard Utilities for Beautiful Commented Code

" Any fool can write code that a computer can understand.
Good programmers write code that humans can understand. "
--- Martin Fowler, Refactoring: Improving the Design of Existing Code


ABAP pretty printer is good for code but not supportive for data declarations.
As a result ABAP code often tends to need more labour to maintain.

I therefore wrote 3 Clipboard utilities

1. YClipJNC based on
http://sap.ionelburlacu.ro/abap/sap2/Beautify_Source_Code.html
but improved to include table~column half-line commenting.
Take any ABAP code (should have survived syntax check) including that "." DOT terminator.
Copy to Clipboard & F8 on YClipJNC will create the beautiful version in clipboard
ready for pasting back.

The big advantage is that you can send for beautification only portions of code
- this is vital in maintaining someone else's badly written code.

2. YClip2JNC is for Internal Table declarations only, with existing comments
Aligns everything nicely.

3. YClip3JNC is to create a Commented CALL FUNCTION Pattern.
Try with "CS_BOM_EXPL_MAT_V2"
The pattern is ready in clipboard for pasting appropriately.

These 3 utilities should give a big boost to good clean commented ABAP
code.

=========================================================================================

*&---------------------------------------------------------------------*
*& Beautify ABAP Code Via Clip
*
*&---------------------------------------------------------------------*

* based on ideas from
* http://sap.ionelburlacu.ro/abap/sap2/Beautify_Source_Code.html

* Author Jayanta Narayan Choudhuri
* Flat 302
* 395 Jodhpur Park
* Kolkata 700 068
* Email sss@cal.vsnl.net.in
* URL: http://www.geocities.com/ojnc

*-----------------------------------------------------------------------
* This program takes ABAP code in ClipBoard, and does the following:
* - Attempts to move comments to the end of the line
* - Adds comments (table name) for the tables listed after a TABLES
* statement if the line has not been commented already.
* - Adds comments (field name) for data elements, parameters, and
* select-options that are defined using the LIKE or FOR statement
* - For ENDLOOP/ENDSELECT adds comment identify the LOOP/SELECT
* that is being closed
* - For FORM/ENDFORM adds comment identify the FORM that is being
* closed
* - Calls function PRETTY_PRINTER to do the SAP standard pretty print
* after the custom comments have been created
* Returns Modified Code Via ClipBoard
*-----------------------------------------------------------------------

REPORT yclipjnc. .

TABLES:
e071 , " Change & Transport System: Object Entries of
Requests/Tasks
tadir , " Directory of Repository Objects
trdir , " Generated Table for View TRDIR
dd02t . " R/3 DD: SAP table texts

DATA: BEGIN OF mtab_old_prog OCCURS 0,
line(172) TYPE c,
END OF mtab_old_prog.

DATA: BEGIN OF mtab_new_prog OCCURS 0,
line(172) TYPE c,
END OF mtab_new_prog.

DATA: BEGIN OF mtab_jnc_prog OCCURS 0,
line(172) TYPE c,
END OF mtab_jnc_prog.

DATA:
* Hold an entire statement, even if it spans multiple lines
BEGIN OF mtab_long_line OCCURS 0,
start TYPE i,
end TYPE i,
code(9999) TYPE c, "For type "C", a maximum length specification of 65535 is allowed.
END OF mtab_long_line.

DATA: BEGIN OF mtab_tabname OCCURS 0,
tabname LIKE dd02t-tabname, " Table name
tabdesc LIKE dd02t-ddtext, " Short text describing ABAP/4
Dictio
END OF mtab_tabname.

* Queue to hold list of internal table names for commenting the ENDLOOP
* line
DATA: BEGIN OF mtab_itab_names OCCURS 0,
tabname(40) TYPE c,
END OF mtab_itab_names.

* Queue to hold list of table names for commenting the ENDSELECT line
DATA: BEGIN OF mtab_tab_names OCCURS 0,
tabname(40) TYPE c,
END OF mtab_tab_names.

DATA: BEGIN OF mtab_form_names OCCURS 0,
tabname(40) TYPE c,
END OF mtab_form_names.

DATA: mylength TYPE i,
myrc TYPE i.

CONSTANTS: myhats(40) VALUE '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'.

* START of EXECUTION

* Read the program code in ClipBoard into an internal table
CALL METHOD cl_gui_frontend_services=>clipboard_import
IMPORTING
data = mtab_old_prog[]
length = mylength.

IF sy-subrc NE 0.
WRITE: / `Unable to read ClipBoard`.
WRITE: / `Exiting program`.
ENDIF.

PERFORM create_condensed_table TABLES mtab_old_prog mtab_long_line.

PERFORM format_program.

CALL FUNCTION `PRETTY_PRINTER`
EXPORTING
inctoo = space
TABLES
ntext = mtab_jnc_prog
otext = mtab_new_prog
EXCEPTIONS
enqueue_table_full = 1
include_enqueued = 2
include_readerror = 3
include_writeerror = 4
OTHERS = 5.

* Write the beautiful program code to ClipBoard from internal table
CALL METHOD cl_gui_frontend_services=>clipboard_export
IMPORTING
data = mtab_jnc_prog[]
CHANGING
rc = myrc.

LOOP AT mtab_jnc_prog.
IF mtab_jnc_prog = space.
SKIP 1.
ENDIF.
WRITE: / mtab_jnc_prog.
ENDLOOP. " LOOP AT MTAB_JNC_PROG


*---------------------------------------------------------------------*
* FORM CREATE_CONDENSED_TABLE *
*---------------------------------------------------------------------*
* Create a table that has all statements condensed onto 1 line *
*---------------------------------------------------------------------*
FORM create_condensed_table
TABLES ftab_old_prog STRUCTURE mtab_old_prog
ftab_long_line STRUCTURE mtab_long_line.

DATA:
* Structure to hold program code/comment
BEGIN OF fstr_line,
code(172) TYPE c, " Program Code
comment(172) TYPE c, " Inline comments
END OF fstr_line.

LOOP AT ftab_old_prog.

IF ftab_long_line-start = 0.
ftab_long_line-start = ftab_long_line-end + 1.
CLEAR ftab_long_line-end.
ENDIF.

* Strip off any inline comments so they do not get in the way
* If comments are not separated, then words in the comments could
* look like keywords, and cause problems
SPLIT ftab_old_prog-line AT `"` INTO fstr_line-code fstr_line-comment.

* Align all statements to be left justified
SHIFT fstr_line-code LEFT DELETING LEADING space.

* Put all lines that make up a single statement into one field
* This will make it easier to isolate key words. For example, if you
* want to process a TABLES statement, but exclude the TABLES part of a
* function call, or a subroutine call.
CONCATENATE ftab_long_line-code fstr_line-code
INTO ftab_long_line-code SEPARATED BY space.

IF fstr_line-code CA `.` OR " Period means end of statement
fstr_line-code(1) = `*` OR " Comment Line
fstr_line-code CO space. " Blank Line
* Keep track of the table index that the statement ends on
ftab_long_line-end = sy-tabix.
* Remove delimiter from concatenation of fields
SHIFT ftab_long_line-code LEFT BY 1 PLACES.

APPEND ftab_long_line.

CLEAR: ftab_long_line-code, ftab_long_line-start.

* Don`t clear out fstr_long_line-end yet. It is used to calc
* fstr_long_line-start.
ENDIF.

ENDLOOP. " LOOP AT FTAB_OLD_PROG
ENDFORM. " FORM CREATE_CONDENSED_TABLE

*---------------------------------------------------------------------*
* FORM FORMAT_PROGRAM *
*---------------------------------------------------------------------*
FORM format_program.

DATA: lstr_old_prog LIKE LINE OF mtab_old_prog.

LOOP AT mtab_long_line.

TRANSLATE mtab_long_line-code TO UPPER CASE.

IF mtab_long_line-code(1) EQ `*`. " Do not modify Comment Lines

LOOP AT mtab_old_prog FROM mtab_long_line-start
TO mtab_long_line-end.

mtab_new_prog-line = mtab_old_prog-line.

APPEND mtab_new_prog.

ENDLOOP. " LOOP AT MTAB_OLD_PROG

ELSEIF mtab_long_line-code(6) EQ `TABLES`.

* Reformat any TABLES statements. Will only reformat when TABLES
* is at the start of the statement. Will not try to get table
* descriptions for CALL FUNCTIONS or FORM/PERFORMs

* Get the table names from mstr_long_line.
PERFORM get_table_names_from_statement TABLES mtab_tabname
USING mtab_long_line-code.
* Find the descriptions for each table
PERFORM get_table_descriptions TABLES mtab_tabname.

* create the new statement
PERFORM build_new_tables_statement USING mtab_long_line.

ELSE. " All other modifications to the code handled here

LOOP AT mtab_old_prog FROM mtab_long_line-start
TO mtab_long_line-end.

* Remove extra spaces from line for comparisons
lstr_old_prog-line = mtab_old_prog-line.
CONDENSE lstr_old_prog-line.
TRANSLATE lstr_old_prog-line TO UPPER CASE.

IF lstr_old_prog-line CS `"`. " Comments
mtab_new_prog-line = mtab_old_prog-line.
ELSE.
IF lstr_old_prog-line CS ` LIKE ` OR
lstr_old_prog-line CS ` TYPE ` OR
lstr_old_prog-line CS ` FOR ` OR
lstr_old_prog-line CS `~`. "jnc OpenSQL
table~column


PERFORM get_for_like_comment USING mtab_old_prog
CHANGING mtab_new_prog.

ELSEIF lstr_old_prog-line(8) = 'LOOP AT'.

* save table name into a queue
PERFORM enqueue_itab_name USING mtab_long_line-code.
mtab_new_prog-line = mtab_old_prog-line.

ELSEIF lstr_old_prog-line(7) = `ENDLOOP`.

* get name off of queue and add it as a comment to the ENDLOOP line
PERFORM add_comment_to_endloop USING mtab_old_prog-line
CHANGING mtab_new_prog-line.

ELSEIF lstr_old_prog-line(7) EQ 'SELECT' AND
lstr_old_prog-line(13) NE 'SELECT SINGLE'.

* save table name into a queue
PERFORM enqueue_tab_name USING mtab_old_prog-line.
mtab_new_prog-line = mtab_old_prog-line.
ELSEIF lstr_old_prog-line(9) = `ENDSELECT`.

* get name off of queue and add it as a comment to the ENDSELECT
PERFORM add_comment_to_select USING mtab_old_prog-line
CHANGING mtab_new_prog-line.

ELSEIF lstr_old_prog-line(5) = 'FORM'.

* save form name into a queue
PERFORM enqueue_form_name USING mtab_old_prog-line.
mtab_new_prog-line = mtab_old_prog-line.

ELSEIF lstr_old_prog-line(7) = `ENDFORM`.

* get name off of queue and add it as a comment to the ENDFORM
PERFORM add_comment_to_endform USING mtab_old_prog-line
CHANGING mtab_new_prog-line.
ELSE. " Any other lines
mtab_new_prog-line = mtab_old_prog-line.
ENDIF.
ENDIF.

APPEND mtab_new_prog.

ENDLOOP. " LOOP AT MTAB_OLD_PROG
ENDIF.
ENDLOOP. " LOOP AT MTAB_LONG_LINE

ENDFORM. " FORM FORMAT_PROGRAM


*---------------------------------------------------------------------*
* FORM GET_TABLE_NAMES_FROM_STATEMENT *
*---------------------------------------------------------------------*
FORM get_table_names_from_statement TABLES ftab_tabname
STRUCTURE mtab_tabname
USING fc_statement.

CLEAR ftab_tabname.
REFRESH ftab_tabname.

REPLACE `TABLES` WITH space INTO fc_statement.
TRANSLATE fc_statement USING `. `. " Replace periods
TRANSLATE fc_statement USING `, `. " Replace commas
TRANSLATE fc_statement USING `: `. " Replace colons

CONDENSE fc_statement. " Remove all extra spaces

SPLIT fc_statement AT space INTO TABLE ftab_tabname.

ENDFORM. " FORM GET_TABLE_NAMES_FROM_STATEMENT


*---------------------------------------------------------------------*
* FORM GET_TABLE_DESCRIPTIONS *
*---------------------------------------------------------------------*
FORM get_table_descriptions TABLES ftab_tabname STRUCTURE mtab_tabname.

LOOP AT ftab_tabname.
SELECT SINGLE * FROM dd02t
WHERE tabname = ftab_tabname-tabname
AND ddlanguage = sy-langu.

IF sy-subrc = 0.
ftab_tabname-tabdesc = dd02t-ddtext.
MODIFY ftab_tabname.
ENDIF.

ENDLOOP. " LOOP AT FTAB_TABNAME
ENDFORM. " FORM GET_TABLE_DESCRIPTIONS


*---------------------------------------------------------------------*
* FORM BUILD_NEW_TABLES_STATEMENT *
*---------------------------------------------------------------------*
FORM build_new_tables_statement USING fstr_long_line LIKE
mtab_long_line.

DATA: lc_sep(1) TYPE c,
li_rows TYPE i,
wordlen TYPE i.

DESCRIBE TABLE mtab_tabname LINES li_rows.

mtab_new_prog-line = `TABLES:`.
APPEND mtab_new_prog.

LOOP AT mtab_tabname.
IF sy-tabix = li_rows.
lc_sep = `.`.
ELSE.
lc_sep = `,`.
ENDIF.

wordlen = STRLEN( mtab_tabname-tabname ).

IF wordlen < wordlen =" 12" wordlen =" 1." line =" f_old_prog." f_new_prog =" f_old_prog." f_new_prog =" lstr_old_prog-line." tabname =" `SY`." tabname =" `SYST`." tabname =" lstr_field-tabname" nametab =" ltab_nametab" internal_error =" 1" table_has_no_fields =" 2" table_not_activ =" 3" no_texts_found =" 4" others =" 5."> 0.
* MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
* WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.

READ TABLE ltab_nametab
WITH KEY tabname = lstr_field-tabname
fieldname = lstr_field-fldname.

IF sy-subrc = 0.

wordlen = STRLEN( lstr_old_prog-line ).

IF wordlen < wordlen =" 45" wordlen =" 1." f_new_prog =" lstr_old_prog-line." mtab_itab_names =" lc_itab." f_prog_line =" mtab_old_prog-line." mtab_tab_names =" lc_tab." f_prog_line =" mtab_old_prog-line." f_prog_line =" mtab_old_prog-line." mtab_form_names =" lc_tab." enqueue_form_name ="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="="" cl_gui_frontend_services="">clipboard_import
IMPORTING
data = mtab_old_prog[]
length = mylength.

IF sy-subrc NE 0.
WRITE: / `Unable to read ClipBoard`.
WRITE: / `Exiting program`.
ENDIF.

* SAP pads non-displayable characters in last few lines
LOOP AT mtab_old_prog.
MOVE sy-tabix TO saveix.

CONDENSE mtab_old_prog-line.
REPLACE ALL OCCURRENCES OF ' ,' IN mtab_old_prog-line WITH ','.

SPLIT mtab_old_prog-line AT space INTO TABLE i_words.

LOOP AT i_words.
wordlen = STRLEN( i_words-word ).
IF wordlen > 60
OR i_words-word+0(1) <> '~'.
DELETE mtab_old_prog INDEX saveix.
EXIT.
ENDIF.
ENDLOOP.
ENDLOOP.

LOOP AT mtab_old_prog.
CONDENSE mtab_old_prog-line.
REPLACE ALL OCCURRENCES OF ' ,' IN mtab_old_prog-line WITH ','.

SPLIT mtab_old_prog-line AT space INTO TABLE i_words.

MOVE 1 TO colnum.
LOOP AT i_words.
wordlen = STRLEN( i_words-word ).
CASE colnum.
WHEN 1.
IF wordlen > maxlen1.
MOVE wordlen TO maxlen1.
ENDIF.
WHEN 2.
IF wordlen > maxlen2.
MOVE wordlen TO maxlen2.
ENDIF.
WHEN 3.
IF wordlen > maxlen3.
MOVE wordlen TO maxlen3.
ENDIF.
ENDCASE.
ADD 1 TO colnum.
ENDLOOP.
ENDLOOP.


LOOP AT mtab_old_prog.
workline = mtab_old_prog-line.
CONDENSE workline.
REPLACE ALL OCCURRENCES OF ' ,' IN mtab_old_prog-line WITH ','.

SPLIT workline AT space INTO TABLE i_words.

LOOP AT i_words.
FIND i_words-word IN mtab_old_prog-line MATCH OFFSET wordoff.
EXIT.
ENDLOOP.
EXIT.
ENDLOOP.

LOOP AT mtab_old_prog.
CONDENSE mtab_old_prog-line.
REPLACE ALL OCCURRENCES OF ' ,' IN mtab_old_prog-line WITH ','.

SPLIT mtab_old_prog-line AT space INTO TABLE i_words.

MOVE 1 TO colnum.
LOOP AT i_words.
wordlen = STRLEN( i_words-word ).
CASE colnum.
WHEN 1.
PERFORM f_diff_calc USING maxlen1 wordlen
CHANGING diff.
CONCATENATE myhats+0(wordoff) i_words-word myhats+0(diff) INTO
mtab_new_prog-line.
WHEN 2.
PERFORM f_diff_calc USING maxlen2 wordlen
CHANGING diff.
CONCATENATE mtab_new_prog-line i_words-word myhats+0(diff) INTO
mtab_new_prog-line.
WHEN 3.
PERFORM f_diff_calc USING maxlen3 wordlen
CHANGING diff.
CONCATENATE mtab_new_prog-line i_words-word myhats+0(diff) INTO mtab_new_prog-line.
WHEN OTHERS.
CONCATENATE mtab_new_prog-line ` ` i_words-word INTO mtab_new_prog-line.
ENDCASE.
ADD 1 TO colnum.
ENDLOOP.
TRANSLATE mtab_new_prog-line USING `^ `.
APPEND mtab_new_prog.
ENDLOOP.

* Write the beautiful program code to ClipBoard from internal table
CALL METHOD cl_gui_frontend_services=>clipboard_export
IMPORTING
data = mtab_new_prog[]
CHANGING
rc = myrc.

LOOP AT mtab_new_prog.
IF mtab_new_prog = space.
SKIP 1.
ENDIF.
WRITE: / mtab_new_prog.
ENDLOOP. " LOOP AT MTAB_NEW_PROG

*&--------------------------------------------------------------------*
*& Form f_diff_calc
*&--------------------------------------------------------------------*
FORM f_diff_calc USING value(maxlen) TYPE i
value(wordlen) TYPE i
CHANGING diff TYPE i.

COMPUTE diff = maxlen - wordlen + 4.
IF diff > 40.
diff = 40.
ENDIF.
ENDFORM. "f_diff_calc


=========================================================================================

*&---------------------------------------------------------------------*
*& Beautiful Function Module Call via clipboard
*
*&---------------------------------------------------------------------*

* Author Jayanta Narayan Choudhuri
* Flat 302
* 395 Jodhpur Park
* Kolkata 700 068
* Email sss@cal.vsnl.net.in
* URL: http://www.geocities.com/ojnc

*-----------------------------------------------------------------------
* This program takes a parameter as a Function Module Name
* and does a documented "pattern paste"
* Returns pattern Code Via ClipBoard
*-----------------------------------------------------------------------

PROGRAM yclip3jnc.

TYPE-POOLS : slis.

PARAMETERS: p_func LIKE fupararef-funcname. " Name of Function Module

DATA : BEGIN OF i_tab OCCURS 0,
funcname LIKE fupararef-funcname, " Name of Function Module
paramtype LIKE fupararef-paramtype, " Parameter type
pposition LIKE fupararef-pposition, " Internal Table, Current Line Index
optional LIKE fupararef-optional, " Optional parameters
parameter LIKE fupararef-parameter, " Parameter name
defaultval LIKE fupararef-defaultval, " Default value for import parameter
structure LIKE fupararef-structure, " Associated
Type of an Interface Parameter
stext LIKE funct-stext, " Short text
END OF i_tab.

DATA: BEGIN OF mtab_new_prog OCCURS 0,
line(172) TYPE c,
END OF mtab_new_prog.

DATA: funcdesc LIKE tftit-stext, " Short text for function module
mylen TYPE i,
myrc TYPE i.

CONSTANTS: myhats(40) VALUE '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'.

TRANSLATE p_func TO UPPER CASE.

SELECT SINGLE
tftit~stext " Short text for function module
INTO funcdesc
FROM tftit
WHERE tftit~funcname = p_func
AND tftit~spras = sy-langu.

TRANSLATE p_func TO LOWER CASE.

CONCATENATE `CALL FUNCTION ` p_func ` " ` funcdesc INTO mtab_new_prog-line.
APPEND mtab_new_prog.

TRANSLATE p_func TO UPPER CASE.

SELECT
fupararef~funcname " Name of Function Module
fupararef~paramtype " Parameter type
fupararef~pposition " Internal Table, Current Line Index
fupararef~optional " Optional parameters
fupararef~parameter " Parameter name
fupararef~defaultval " Default value for import parameter
fupararef~structure " Associated Type of an Interface
Parameter
funct~stext " Short text
INTO TABLE i_tab
FROM fupararef
INNER JOIN funct
ON fupararef~funcname = funct~funcname
AND fupararef~parameter = funct~parameter
AND funct~spras = sy-langu
WHERE fupararef~funcname = p_func
AND fupararef~r3state = 'A'
ORDER BY fupararef~paramtype
fupararef~pposition.


LOOP AT i_tab.
AT NEW paramtype.
CASE i_tab-paramtype.
WHEN 'C'.
MOVE ' CHANGING' TO mtab_new_prog-line.
WHEN 'E'.
MOVE ' IMPORTING' TO mtab_new_prog-line.
WHEN 'I'.
MOVE ' EXPORTING' TO mtab_new_prog-line.
WHEN 'T'.
MOVE ' TABLES' TO mtab_new_prog-line.
WHEN 'X'.
MOVE ' EXCEPTIONS' TO mtab_new_prog-line.
ENDCASE.
APPEND mtab_new_prog.
ENDAT.

IF i_tab-optional = 'X'.
mtab_new_prog-line = `*^^^`.
ELSE.
mtab_new_prog-line = `^^^^`.
ENDIF.

IF i_tab-paramtype = 'X'.
MOVE i_tab-pposition TO i_tab-defaultval.
CONDENSE i_tab-defaultval.
ELSE.
TRANSLATE i_tab-parameter TO LOWER CASE.
ENDIF.

CONCATENATE mtab_new_prog-line i_tab-parameter '^=^' INTO mtab_new_prog-line.

IF i_tab-defaultval IS NOT INITIAL.
CONCATENATE mtab_new_prog-line i_tab-defaultval INTO mtab_new_prog-line.
ENDIF.

mylen = STRLEN( mtab_new_prog-line ).

IF mylen < mylen =" 31" mylen =" STRLEN(" mylen =" 47" mtab_new_prog =" space." cl_gui_frontend_services="">clipboard_export
IMPORTING
data = mtab_new_prog[]
CHANGING
rc = myrc.

No comments: