wiki:ErlangShellInterface

Version 19 (modified by manualwiki, 12 years ago) (diff)

fbs

RefactorErl Console Interface

When running RefactorErl, you can control the tool via function calls typed into the Erlang shell. RefactorErl has many features, and is quite a complex system with dozen of functions whose name we do not expect our users to remember. In order to ease and unify the command-level access to all the analysis and refactoring functionality, we have designed a group of Erlang functions that cover the most frequently used features of the tool. The module these functions are located in is called ri, you can use this module to interact easily with the tool. You can add files/directories to the database, run semantic queries, create backups, or even do transformations via this Erlang-level interface.

Command-line help

Help can be acquired in ri with

ri:help().

or even shorter as

ri:h().

This function lists several topics, on which further help is available by

ri:h(Topic).

If you need specific help with a function, simply call it with an _h postfix to the name. For example, help for the function add is available by calling

ri:add_h()

Compiling the tool

The tool can be compiled/recompiled by invoking

ri:build().

You can also specify build parameters, but this feature is mostly applied through the development, so please find the module documentation for the details. Note that this build function tries to compile the NIF graph representation as well, if these have not been compiled yet. If you want to prevent this, you can use the no_nif build parameter.

Managing files

You can add files to the RefactorErl database by calling the add function with either a filename as a string or a module name as an atom. Note that in the latter case, "ri" defaults to the current working directory (which you may work around by including a path in your singe-quoted atom). If you specify a directory instead of a regular filename, then it will be recursively traversed. You may just as well give a list of atoms or strings to add more files at once. All of the following example commands would add the same file:

 cd(dir), ri:add(modname).
 ri:add('dir/modname').
 ri:add(['dir/modname']).
 ri:add("dir/modname.erl").
 ri:add("/current/dir/modname.erl").

The module displays the progression of loading. Removing files from the database is similarly easy and also recursive, except for one difference. You need not restrict yourself to dropping a module relative to the current directory, but in exchange you must use real module names that do not contain path delimiters. The following will equally work:

 ri:drop(modname).
 ri:drop([modname]).
 ri:drop("dir/modname.erl").
 ri:drop("/current/dir/modname.erl").

Modules can be loaded as applications, but the base of your library has to be set before:

ri:addenv(appbase, "path/to/my/applib").

You can check the already given application base directories:

ri:envs().

Let's see an example:

% Here 'appbase' contains 'usr'.
(refactorerl@localhost)18> ri:envs().
output = original
appbase = "/usr/local/lib/erlang/lib"

(refactorerl@localhost)19> ri:add(usr, synatx_tools).
Application synatx_tools not found under usr
not_found

% 'appbase' contains 'usr', so syntax_tools will be loaded from
% '/usr/local/lib/erlang/lib'
(refactorerl@localhost)20> ri:add(usr, syntax_tools).
Adding: /usr/local/lib/erlang/lib/syntax_tools-1.6.7.1/src
...

You can also set include directories to your include files using:

ri:addenv(include, "path/to/my/include").

It is possible to delete the defined environment variables:

ri:delenv(include).

Or you can set an environmental variable to another value:

ri:setenv(env_name, "path/to/new_value").

For convenience, both the filenames and the directory names can be given as atoms as well as strings. The list of loaded files can be obtained by calling

ri:ls().

This call also displays the status of the loaded files (error or no_error). If the module m is loaded,

ri:ls(m).

will give information about the functions, records and macros in the file. The contents of a file can be listed by

ri:cat(m).

Usually, Erlang source files (having the extension .erl) are loaded into RefactorErl. In addition, RefactorErl is also capable of loading compiled .beam files.

ri:add("compiled.beam").

Note that this feature is applicable only to those .beam files that were compiled with the debug_info option. Also note that the resulting file will be pretty printed by RefactorErl.

Using transformations

Transformations can be called using their abbreviated names, and the list of required parameters. These commands are listed in refactoring functionalities. There is another way to call a transormation. This way let the user to choose: user wants to specify all of arguments or not. There are lots of cases when the user can not specify all of the required arguments. In this case the tool can help the user with interactions. The tool ask questions and the user has to answer it to specify the missing arguments. The interactions also work if there are problems with the given arguments.

Manipulating the graph

You can reset the database by invoking

ri:reset().

This will remove all loaded files. This function should be called if the graph gets corrupted. You can add a checkpoint using

ri:backup().

If the transformations you have performed are not satisfactory, you can go back to the previous checkpoint using

ri:undo().

If you use NIF graph database, then it is little different:
You can create backups with ri:backup/0 or ri:backup/1 and you can load these backups with ri:restore/1. When execute a transformation a backup will be created, which name differs from the ordinary backups, and the ri:undo/0 function will restore that.

Inspecting the graph

You can draw the semantic representation graph of RefactorErl by calling

ri:graph().

This function produces a .dot file (by default, graph.dot, although this can be customised), which can be transformed to several visual formats using Graphviz. One of these transformations is available from RefactorErl for convenience:

ri:svg().

The representation can be altered:

ri:svg(OutFile, Filter).

where Filter is one of the following:

  • all: default, all edges except environmental ones are shown.
  • syn: only syntactic edges are shown.
  • sem: only semantic edges are shown.
  • lex: only lexical edges are shown.
  • all_env: all edges are shown, no filtering.
  • ctx: context related edges are shown.
  • not_lex: all edges except lexical ones are shown.
  • dataflow: dataflow related edges are shown.
  • a list of the above: shows the union of the designated subgraphs.

Using queries

Queries can be invoked by either

ri:q(Query).

or

ri:q(Module, Regexp, Query).

The former is applicable when a query starts generally, such as

ri:q("mods.funs.name").

For those queries that begin from a selected position (these queries start with "@" when used from Emacs), the second variant is required. As the console cannot mark a position, the first and the second component indicate the starting point for the query. The following example shows how to get all the variables used in the body of the function f/2 from the module m.

ri:q(m, "f\\(X, Y\\)", "@fun.var").

Additional options can be given to a semantic query in a proplist as the last argument. The following arguments are currently recognized:

  • {out,FileName}: write the textual output of a query to a file.
  • linenum: prepends match sites with file and line number information.

similar to grep -n. The following example outputs all defined functions with line numbers to a file named result.txt.

ri:q("mods.funs",[linenum,{out,"result.txt"}]).

There is a semantic queries page, where you can learn more about this topic.

Analysis

Dependency analysis on function or module level

There is a Module and Function Dependencies page, where you can learn more about this topic.

The command-line interface offers two interface functions, which are:

  1. For drawing:
    ri:draw_dep/1
    
  2. For printing the result to stdout:
    ri:print_dep/1
    

Options

The parameter of the interface functions is a proplist setting the options of the analysis. The available options are:

  • level (mod | func)

The level of the dependency query (module or function).

  • type (all | cycles)

Whether the investigation should be done on the whole graph, or just on the cyclic part (if exists). When printing out the cycles, type all returns graph nodes, while cycles returns names.

  • otp (true | false)

Whether Erlang/OTP standard modules should be included in the analysis or not.

  • gnode

List of entity or entities that should be the starting point of the analysis. Especially at function level, the list is compulsory when the functions are identified by their module, name, arity.

  • exception

List of entities excluded from the analysis.

  • leaves

List of those entities which should be included in the analysis, but their children should not (and consequently the children become exceptions).

  • dot

The file path of the generated .dot graph description. Unless it is a non-existing absolute path, the graph will be placed into the ./dep_files directory. This option is only available when using draw_dep.

You can specify entities either with graph nodes (such as {'$gn', func, 123}) or with their identifier. Modules can be specified with their names as atoms (e.g. 'mnesia'), while functions are specified by their MFA descriptor as a string (e.g. "io:format/2")

Examples for listing results

  • Checking for cycles in module level.
    ri:print_dep([{level, mod}, {type, all}]).
    

  • Checking for cycles in function level, and printing out names of the functions (Module:Function/Arity).
    ri:print_dep([{level, func}, {type, cycles}]).
    
      [['foo:fv4/1','foo:fv4/1'],
      ['test3:p/1','test:fv6/1','test3:p/1'],
      ['cycle4:f4/1','cycle3:f3/1','cycle4:f4/1'],
      ['cycle2:fv2/1','cycle1:fv1/0','cycle2:fv2/1'],
      ['test:fv5/1','test:fv4/2','test:fv5/1'],
      ['cycle4:f5/1','cycle3:f6/1','cycle4:f5/1']]
    
  • Checking for cycles in function level, and printing out the graph nodes of the functions.
    ri:print_dep([{level, func}, {type, all}]).
    
      {"6 cycle(s)",
      {[[{'$gn',func,28},{'$gn',func,28}], 
      [{'$gn',func,29},{'$gn',func,37},{'$gn',func,29}],
      [{'$gn',func,7},{'$gn',func,9},{'$gn',func,7}],
      [{'$gn',func,2},{'$gn',func,1},{'$gn',func,2}],
      [{'$gn',func,36},{'$gn',func,35},{'$gn',func,36}],
      [{'$gn',func,8},{'$gn',func,6},{'$gn',func,8}]]}
    
  • Checking for cycles in module level from a given node
    ri:print_dep([{level, mod}, {gnode, {'$gn', module, 24}}]). 
    
      {true,[[{'$gn',module,24},
              {'$gn',module,25},
              {'$gn',module,24}]]}
    
  • Checking for cycles in function level from a node given with its identifier
    ri:print_dep([{level, func}, {gnode, ["cycle4:f5/1"]}]).
    

Function block dependencies

In large systems, sets of applications (which themselves consist of several modules) are organised into bigger units; keeping in line with Ericsson terminology, we shall call these function blocks. We also seek dependencies between them, which is conceptually similar to dependencies between modules: a function block FB1 is dependent on a function block FB2 if a module from FB1 is dependent on one from FB2. This examination is also available from the command-line interface. You can read about the usage and about the topic on Function blocks page.

Logical layers analysis

In large program systems, groups of compilation units (in the case of Erlang, modules) usually form logical layers. A desired property of such systems is that code in one layer should only use the layer immediately below it, and conversely, provide functionality only for the layer immediately above it. If you would like to check whether a system observes this rule, you should visit the Interface Layers page, which show you how to check it.

Server management command list

Here's the list of supported server management commands:

  • add(FDML): add a module, file, directory or a list of these to the database.
  • drop(FDML): drop a module from the database.
  • ls(): list files that are in the database.
  • backup(): update the backup (checkpoint).
  • undo(): undo the transformation (rollback, only one step).
  • clean(): clean backups (delete all checkpoints).
  • reset(): reset the database to an empty state, but valid schema.
  • graph(Target): assume no options and call one of the next two.
  • graph(Atom,Options): assume ".dot" extension and call the one below.
  • graph(File,Options): draw the graph with the given options.
  • svg(): draw the graph to graph.svg and call Graphviz.
  • svg(File)
  • svg(File, Options)

The additional/modied commands, that you can use, if you use the NIF database engine:

  • backup(): creates a backup.
  • backup(CommitLog): creates a backup as ri:backup/0, but here the user can attach a commit log to the backup file.
  • ls_backups(): returns a lists of backups, that has been created before with ri:backup/0 or ri:backup/1.
  • backup_info(Backup): returns information about the given backup.
  • restore(Backup): restores the given backup.
  • create_graph(Name): creates a graph with the given name.
  • rename_graph(OldName, NewName): renames a graph that has the given OldName, with the given NewName.
  • ls_graphs(): returns a list of the created graphs.
  • actual_graph(): returns the actual graph's name.
  • load_graph(Name): loads the given graph.
  • delete_graph(Name): removes the given graph.
  • delete_all_graphs(): removes all graphs.