Tfman tutorial

Maciej Helminiak <tfman.project@opmbx.org>
modified: 2014-07-15



Whole document needs improvements !!!

1. Description

General

Tfman is text file manager managing files via textual representation of file system (being ordinary text). It can scan directories to create textual representation or parse textual representation to retrieve actions specified by user.

. Written in C for Unix-like systems.
. Does not depend on external libraries.
. Can cooperate with command-line tools, like Sed, via standard input and output streams.
. Can be integrated with text editing tools, like Vim.

Tfman has two working modes: scan mode and parse mode.

Scan mode

The scan mode performs scanning on specified part of file system (directory or whole tree of directories) and composes textual representation of it. Result is send to standard output (this is default) or written to a file.

Created textual representation is formatted in a manner allowing direct use in parse mode. User can edit it as normal text and add action tags.

Parse mode

The parse mode retrieves actions specified in textual representation and performs them on file system. Textual representation is read from standard input (default) or from file. Multiple actions of same or different types can be specified on multiple entries, whole directories and groups.

The textual representation must be in specific format (but it is still ordinary text) and actions are named by tags.

tfm

Tfm is convenience tool that combines scan mode and parse mode to make Tfman use easier. It does scanning of directories, editing textual representation and parsing result in one step.

2. Introduction

This section aims to be a quick introduction to the idea behind Tfman. Following examples present the way Tfman works and a few of its features. It does not aim to show the most effective way to use the program or to discuss all the features in detail.

Manage files in specfied directories tree

The quickest way to make changes in a file system is probably Tfm tool, which allows direct jump into editing textual representation of given directory or whole directories tree. Issue this command to scan current directory resursively 3 levels deep and automatically open its textual representation for editing in program specified by 'EDITOR' shell variable:

  tfm . -R=3

Now you can quickly find desired files with editor's search tools and add action tags. For example:

  {
  | [name] *                    DELETE ENTRY
  | [name2] ~                   CREATE ENTRY
  | [name3] >>tar               MOVE
  | [name4] >*tar               OVERWRITE-COPY
  } user:group:rw-rw-rw-        CHANGE OWNING USER, GROUP AND PERMISSIONS
                                OF ALL GROUPPED ENTRIES
   ~ Dir/                       CREATE DIRECTORY - NOTE TRAILING '/' AND SPACE IN FIRST COLUMN
   ~ Dir/file                   CREATE ENTRY

After you write changes and close editor, Tfman will parse resulting text, retrieve operations to perfmorm, show them all to you and ask for confirmation.

If you use Vim

Tfman can be used from within Vim as multi-window file manager. Needed files - that is small plugin and syntax file - should have been installed to Vim's system directory during Tfman installation. If they haven't, copy them from /usr/share/tfman/vim/vimfiles/{plugin,syntax} to corresponding Vim directories.

Invoke Vim and issue following commands to use Tfmvi, text file manager inside Vim (remember you can map commands to keys):

  :Tsr DIR

to open DIR in new or in existing tfman edit window. Edit textual representation adding desired actions, exactly like you usually edit text and parse it with:

  :T

to perform operations and show results in tfman message window, opened for you below edit window. This time Tfman will not ask for confirmation like it did in command-line, so next time you may want to inspect operations first with:

  :Tsh

it will show operations in message window without performing them.

  :Tsw DIR 0

will scan DIR recursively (0 - all the way to bottom) and open result in new edit window. Now you have two edit windows and one message window for them.

  ENTER

press 'ENTER' to scan entry under cursor and open result in current window.

  -

press '-' to scan parent directory (go level up in hierarchy).

  \m

(assuming your mapleader is '\') add move action tag in current line with entry name as target. This way you can quickly rename file.

  \c

add copy action tag in current line

  \r

add remove action tag in current line

Commands work in any window - if you are not currently in tfman edit window, first edit window in tabpage will be used or new window will be opened. Mappings work only in Tfman widnows.

See 'Vim integration' below and consult Tfman manpage for more information.

Creating files structure

Create file with following content, let it be /tmp/t.tfm:

  {
   free_entry1         >/tmp
   /tmp/free_entry2
   free_entry{7,8,9}   :rwxrwxr--
   free_entryA
  } ~
  
  # Dir/         == ~
    # Subdir
      # SubdirA
      | entry1     >>./entryA
      | entry2     >>entryB
      | ./entry3

Indentation is important! Default indentation step is two spaces. It means that every line must be indented by multiple of two. Indentation matters particularly for subdirectories, determining their place in directories hierarchy.

Change to safe directory and invoke Tfman:

  $ tfman /tmp/t.tfm

Tfman will parse textual representation and perform operations specified in text. Operations are performed in fixed order, type by type. First all create operations, then copy, then remove etc. Operations of same type are performed in order of appearance in text.

What all those tags mean? First we wanted to create few files in current directory, begining their names with 'free_entry'. To achieve this we have grouped them using braces '{}' and specified '~' tag for whole group. We also used brace expansion for third entry to save typing and specify three names at once. Then we have decided to copy first entry to '/tmp' directory after it is created. We achieved exactly the same result for second entry, just created it in '/tmp' straightaway, eliminating copy step. We also changed permissions for three files in third entry from default to "rwxrwxr--".

In second part of file we wanted to create whole directory structure with subdirectories and files. Default top directory is current working directory (directory where Tfman was invoked) and it is assumed from begining of file. To replace it for succeding entries we had to put new top directory name 'Dir' before other entries. Everything after, until new top directory is specified, will be related to that directory. Then we have added subdirectories, carefully choosing indentation, because indentation determines to which directory above given subdirectory belongs. Then we have added few entries in one of subdirectories.

But how to create this whole structure? We need to specify create action for all entries belonging to 'Dir'. We have done that with '==' tag which tells Tfman to apply succeding actions to directory itself and to its content - that is all entries below it in text. That means that next create action '~' will create directory, subdirectories and all entries inside. Finally, we have decided to move two entries to different places using move tags '>>'. First entry goes to current working directory under name 'entryA', second entry is just renamed.

Find file in directories tree and rename it

Now invoke Tfman as follows:

  $ tfman Dir -R -C -F=name > /tmp/t2

to make recursive (-R) scan of 'Dir' with entry names cloning (-C) and writing result to '/tmp/t2' file, which should look like this:

  #  [Dir]       Dir
  |  [    ../] ..
  |  [Subdir/] Subdir
  
  #    [Dir/Subdir] Dir/Subdir
  |    [     ../] ..
  |    [SubdirA/] SubdirA
  
  #      [Dir/Subdir/SubdirA] Dir/Subdir/SubdirA
  |      [    ../] ..
  |      [entry1]  entry1
  |      [entry2]  entry2
  |      [entry3]  entry3
  |      [entry4]  entry4
  |      [entry5]  entry5
  |      [entry6]  entry6

Edit file, find name 'entryB', add move action '>>' and modify target name, then save file. Last line should look like this:

  |  [entryB]  >>entryX

Invoke Tfman to parse modified file:

  $ tfman /tmp/t2

It will just rename 'entryB' to 'entryX'.

Remove unneeded files

Now prepend this text to /tmp/t.tfm content:

   * entry*
   * free_entry*
   * /tmp/{t.tfm,t2,free_entry{1,2}}
   * Dir
  END
   ... PREVIOUS FILE CONTENT ...

All created files are now removed and file system is in original state. We have used remove tag '*' to achieve this. 'END' tag terminates parsing immediately, preventing execution of actions in succeding lines.

Terminology

Terms used above that may need some explanation:

current working directory
directory where Tfman was invoked
current parent directory
directory in text that is closest directory above entry, for 'entry1' it is 'Dir/Subdir/SubdirA'
absolute directory
directory whose name begins with '/', relative to file system's root directory

3. Ways of usage

You can pass textual representation to Tfman in several ways. For example, Tfman can read text from standard input. Just invoke Tfman, type text in and finish with ctrl-D:

  $ tfman
   ~ tr         NOTE BEGINING SPACE
  ^D

It will create empty file in current directory. '|' character is needed to make line active, '~' is action tag. First word that do not start with action tag is taken as name. Names can also be enclosed in '[]' brackets. That allows placing them anywhere in the line or begin name with character that would be otherwise recognized as action tag.

Another way is to send text to Tfman's standard input through pipe:

  echo " tr *" | tfman

It will remove file tr.

You could also just create suitable text file by hand and then make Tfman to read it (you can use your favorite text editor instead of echo):

  echo " tr" > f
  tfman f

It has no effect because there is no action defined, despite line is active.

All that is not very useful, nothing you couldn't do with standard console tools.

Better approach is to use Tfman scanning mode to create textual representation for specified part of file system and then edit it to add desired actions. Following example will scan current directory and write its textual representation to file 't':

  tfman . > t

Textual representation is ready to edit and add actions to it. When you done, pass result to Tfman:

  tfman t

Do it quicker

Until now, using Tfman involves several steps: scaning, opening file, editing, saving file and quiting, then parsing and confirming operations. To avoid most of these steps make use of simple shell script installed together with Tfman. Invoke:

  tfm .

It will scan current directory and open result in your favorite text editor, determined from EDITOR environment variable (currently 'vim' and 'nano' are tested to work, but other editros should work as well). If variable is not set or is empty, script defaults to 'vim'. After you save and quit editor, modified result is scanned and actions performed. There is vim syntax highlighting installed with Tfman that should make editing more pleasant.

Passing options to Tfman through Tfm:

  tfm . -C -F=name,type,size

Reuse last textual representation (ie. to correct errors):

  tfm

Help:

  tfm -h

Combine with other command-line tools

Default Tfman behavior is to utilize standard input and output streams, what allows to cimbine it with tools like Sed or Awk and probably others. I will give just one short example how it can be done.

This will move all directories in current directory tree with extension '.tmp' (together with content ofcourse):

  $ tfman DIR -F=name,type -R | sed 's,\(^|.*.tmp/] d$\),\1 >>/tmp/common,' | tfman

4. Basic concepts

Few simple examples of textual representation introducing action tags and names composition. All examples can be parsed by Tfman.

Copy, move and overwrite

   ~ file1 >>target1
   ~ file2 >target2

First both files are created in current working directory. Then 'file1' is moved to 'target1' and 'file2' is copied to 'target2'. If 'file1', 'file2' or any of targets exist Tfman will terminate, same for any other error.

  #
   ~ file1 >>*target1
   ~ file2 >*target2

Above representation works exactly the same way as previous one with very important exception: if any target exist it is removed. We also added directory line with empty name - it doesn't change anything. Empty name in directory line resets top directory to current working directory. We could achieve exactly the same result by specifing current directory explicitly:

  # [.]
   ~ file1 >>*target1
   ~ file2 >*target2

Links and name composition

  # Dir
   symlink1   ->target1
   ./symlink2 ->./target2
   /hlink     =>/hlink

Two symbolic links and one hard link is created, but more interesting thing in above example is how entry and target names are composed.

First entry undergoes normal name composition. It means that directory in text above them is prepended to both name and action operand. Line virtually becomes:

  Dir/symlink1 ->Dir/target1

Second entry has both name and target starting with './', which indicates that file name should be taken as relative to current directory (where Tfman was invoked), not to parent directory (placed in text). Line is not changed:

  ./symlink2 ->./target2

Same for third line where both name and target are absolute:

  /hlink =>/hlink

To overwrite files if they already exist and replace them with newly created links use:

   symlink  ->*target1
   hardlink =>*target2

If 'symlink' or 'hardlink' exists it will be removed prior to link creation.

5. More complex example

Examples showing how Tfman can be used in more complex way.

Create directory tree to work with

Go to safe directory and parse following representation to create whole new structure of files:

  {{
  # Dir1            == ! mh:users:rwxrwxr-x
  | f1              :rwx------
    # SubdirA
    | e1
      # SubdirX
      | ex
      | ey
      # SubdirY     ! am:users
      | ez
    # SubdirB       :rwxr-x---
    | e2
    | e3
  
  # Dir2      = :rwxrwxrwx
  | f2
  | f3
  }} ~

Here we have created desired files structure. Each line starts with line tag which defines line's type. If line doesn't begin with line tag it is not parsed at all. Line types are:

directory: '#'
Defines new parent directory for succeding lines. It is appended to previous directory or one of its ancestors - it depend on indentation. If directory is more indented then previous directory then it is added as its child. If it is equally or less indented then previous directory then it is placed higher in hierarchy. Implicitly, parent directory is set to current working directory when parsing begins. Dot '.' used as directory name sets parent directory to working directory explicitly.
entry: '|' or ' ' (space)
Bar or space marks ordinary entry line belonging to directory above it. It may be regular file, directory or any other type of file in file system.

Both directory trees are gathered in one group. Create action is specified for this whole group. This way we create all files at once. We have used strong grouping ('{{') which groups directories instead of normal grouping ('{') which groups files only.

Following action tags are used in above textual representation to change properties of files. For detailed explanation how ':' tag works consult manpage.

permissions: ':perms'
change permissions to specified by operand
owning user: 'user:'
change user to specified by operand
owning group: ':group'
change group to specified by operand

We also used some special tags to change behavior of succeding actions:

do not terminate on error: '!'
Errors occuring in succeding actions will not cause Tfman to terminate. Error will be printed and Tfman will keep parsing. For our particular case, this means that if there is no 'mh' or 'am' user registered in the system, error will not cause Tfman to terminate.
content only: '='
Following actions affect not directory entry itself but all entry lines given below as its content. We used this to change permissions of all entries inside 'Dir2'.
directory and content: '=='
Following actions affect both, directory and its content. We used this to change owners of all entries inside 'Dir2' and directory itself.

As you can see actions can be specified for groups of entries at once. To affect whole directories use '=' or '==', to affect group of entries use '{{' (includes directories and files) or '{' (includes files only).

Make textual representation of directory tree

Now we have directories we can work with. First we need to make textual representation of it, then we will be able to edit it and add desired actions. Scan both directories recursively:

  $ tfman Dir1 Dir2 -R > t

You will get this in file 't':

  #  [Dir1]   D  rwxrwxr-x      mh  users          --   4096    4096    m.2014-06-17 20:34:04  (none)
  |  [    ../]   d  rwxr-sr-x  23  mh  magazyn        --   4096    4096    m.2014-06-17 20:34:04  (none)
  |  [Subdir/]   d  rwxr-sr-x  5   mh  users          --   4096    4096    m.2014-06-17 20:34:04  (none)
  |  [    f1]    -  rwx------      mh  users          --      0       0    m.2014-06-17 20:34:04  (none)
  
    #  [Dir1/Subdir]   D  rwxr-sr-x     mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
    |  [     ../]   d  rwxrwxr-x  4  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
    |  [SubdirA/]   d  rwxr-sr-x  5  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
    |  [SubdirB/]   d  rwxr-x---  4  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
    |  [subdirC/]   d  rwxr-sr-x  2  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
  
      #  [Dir1/Subdir/SubdirA]   D  rwxr-sr-x     mh  users          --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [     ../]   d  rwxr-sr-x  5  mh  users          --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [SubdirX/]   d  rwxr-sr-x  4  mh  users          --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [SubdirY/]   d  rwxr-sr-x  3  mh  magazyn        --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [     e1]    -  rw-r--r--     mh  users          --      0       0    m.2014-06-17 20:34:04  (none)
  
        #  [Dir1/Subdir/SubdirA/SubdirX]   D  rwxr-sr-x     mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
        |  [../]   d  rwxr-sr-x  5  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
        |  [ex]    -  rw-r--r--     mh  users        --      0       0    m.2014-06-17 20:34:04  (none)
        |  [ey]    -  rw-r--r--     mh  users        --      0       0    m.2014-06-17 20:34:04  (none)
  
        #  [Dir1/Subdir/SubdirA/SubdirY]   D  rwxr-sr-x     mh  magazyn        --   4096    4096    m.2014-06-17 20:34:04  (none)
        |  [../]   d  rwxr-sr-x  5  mh  users          --   4096    4096    m.2014-06-17 20:34:04  (none)
        |  [ez]    -  rw-r--r--     mh  users          --      0       0    m.2014-06-17 20:34:04  (none)
  
      #  [Dir1/Subdir/SubdirB]   D  rwxr-x---     mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [../]   d  rwxr-sr-x  5  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [e2]    -  rw-r--r--     mh  users        --      0       0    m.2014-06-17 20:34:04  (none)
      |  [e3]    -  rw-r--r--     mh  users        --      0       0    m.2014-06-17 20:34:04  (none)
  
      #  [Dir1/Subdir/subdirC]   D  rwxr-sr-x     mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
      |  [../]   d  rwxr-sr-x  5  mh  users        --   4096    4096    m.2014-06-17 20:34:04  (none)
  
  
  #  [Dir2]   D  rwxr-sr-x      mh  magazyn        --   4096    4096    m.2014-06-17 20:34:04  (none)
  |  [../]   d  rwxr-sr-x  23  mh  magazyn        --   4096    4096    m.2014-06-17 20:34:04  (none)
  |  [f2]    -  rwxrwxrwx      mh  magazyn        --      0       0    m.2014-06-17 20:34:04  (none)
  |  [f3]    -  rwxrwxrwx      mh  magazyn        --      0       0    m.2014-06-17 20:34:04  (none)

As you can see tfman wraps entry names with brackets. It allow names to be placed anywhere in the line, not only as first non-action word. Note, that this layout of entry fields is default and can be changed with certain options. Try to parse the file:

  $ tfman t

Not a single action is recognized, despite all lines are active, because... Yes! Not a single action is specified. Now we can edit file and specify actions we desire.

Fields without action tags are ignored thus they can be removed safely. Same apply to whole lines if they are not directories (directory name is lost). Instead of removing lines we can make them inactive by replacing line tag '|' with any other character and then all actions in that line are switched off.

Move files around

Edit textual representation (i have deleted unnecessary entry fileds for readability, you can do the same):

  #  [Dir1]
  |  [    ../]
  |  [Subdir/]
  |  [    f1]    >>Subdir  :rwxr--r--
  
    #  [Dir1/Subdir]
    |  [     ../]
    |  [SubdirA/]
    |  [SubdirB/]
    |  [subdirC/]
  
      #  [Dir1/Subdir/SubdirA]
      |  [     ../]
      |  [SubdirX/]
      |  [SubdirY/]
      |  [     e1]   >>.. :rw-r--r--
  
        #  [Dir1/Subdir/SubdirA/SubdirX]
        |  [../]
        |  [ex]    >>../.. :rw-rw-rw-
        |  [ey]    >>../.. :rw-rw-rw-
  
        #  [Dir1/Subdir/SubdirA/SubdirY]
        |  [../]
        |  [ez]    >>../.. :rw-rw-rw-
  
      #  [Dir1/Subdir/SubdirB]
      |  [../]
      |  [e2]       >>.. :rw-rw-rw-
      |  [e3]       >>.. :rw-rw-rw-
  
      #  [Dir1/Subdir/subdirC]
      |  [../]
  
  
  #  [Dir2] *
  |  [../]
  |  [f2]    >>../Dir1/Subdir :rw-rw-rw-
  |  [f3]    >>../Dir1/Subdir :rw-rw-rw-

All entries are moved to 'Dir1/Subdir' with changed permissions. Additionaly 'Dir2' is removed afterwards.

Rename multiple files

Now scan 'Dir1':

  $ tfman Dir1 -R -C -F=name > t

and edit 't' file to look like this:

  #  [Dir1]       Dir1  
  |  [    e2]      >>e2_new
  |  [    e3]      >>e3_new
  
    #  [Dir1/Subdir]        Dir1/Subdir  
    |  [     e1]       >>e1_new  
    |  [     ex]       >>ex_new  
    |  [     ey]       >>ey_new  
    |  [     ez]       >>ez_new  
    |  [     f1]       >>f1_new  
    |  [     f2]       >>f2_new  
    |  [     f3]       >>f3_new  

Parse it:

  tfman t

It will rename all files.

6. How use Tfman efficiently

I will try to present some tasks that - i think - you can acomplish easier and quicker then with standard command line utilities. Especially if you are using decent text editor. These are examples based on my file system, your results will certainly look different but rules stay the same.

To create whole directory tree (maybe project structure) with desired permissions and owners, write text below to file and parse it with Tfman:

  $ Projname ! *          PRIORITY ENTRY
  # Projname  == ~    john:developers:rwxrws---
    # subdir1         lenny:testers:rwxr-S---
    | fileX
      # subdirA   ==  will:testers:rwxr-S---
      | file{A,B}
      | file{1,2}
    # subdir2         henry:testers:rwxr-S---
    | file3

First line actions affect all entries in file thanks to '==' tag. The same tag let us change permissions and ownership for 'subdirA' and its content. We also change permissions and owners for few single lines. But what the hell the first line is for? This is priority line, thanks to line tag '$'. This type of line is executed before any other normal lines. It will remove 'Projname' and its content before proper directory is created. Thanks to that, Tfman will not terminate trying to create existing directory. But what if 'Projname' does not exist? Tfman would terminate trying to remove it. We prevent this by putting '!', tag that causes succeding tags to ignore errors. Strictly speaking, Tfman does not ignore errors, but only prints information instead of terminating.

  $ Projname ! *          PRIORITY ENTRY
  {{
  # Projname
    # subdir1         lenny:testers:rwxr-S---
    | fileX
  
  {{
      # subdirA
      | file{A,B}
      | file{1,2}
  }} will:testers:twxr-S---
  
    # subdir2         henry:testers:rwxr-S---
    | file3
  }} ~  john:developers:rwxrws---

Above achieves exactly the same but uses grouping, technique the one may find more clear.

To move multiple, selected files at once, scan part of filesystem where you want to do that and then add '>>' tag where desired. For example, I have scanned current directory:

  $ tfm . -F=name

And then, after editing result, i got this:

  | [.]
     rename files
  | [file1] >>f1
  | [fi2e1] >>f2
  | [fi3eA] >>f3
     move files to common destination dir
  {
  | [file1]
  | [fi2e1]
  | [fi3eA]
  } >>/tmp
     move similarly named files from different directory here
  | [/tmp/file{a,b,c}*] >>.

To copy many files proceed in similar way, but this time use Tfman's ability to clone names:

  $ tfm . "-F=name -C"

I got this:

  | [file1] file1
  | [file2] file2
  | [file3] file3
  | [file4] file4
        ...

As you can see, all names are cloned to help you add actions like copy or move, specificaly if you rename many files and just want to slightly adjust their names. For example:

  | [file1] ))file1.bak ++ :rw-------
  | [file2] ))file2.bak ++ :rw-------
  | [file3] ))file3.bak ++ :rw-------
  | [file4] ))file4.bak ++ :rw-------
        ...

This will copy every entry to file with appended suffix. We decided to change target file's permissions, and we used '++' tag for that - it makes succeding actions affect previous action's target. How to explain this clearly? If we would ommit '++', permissions change would affect entry before it is copied and both, entry and target, would have their permissions changed. With '++' change affects only target.

Move and copy files between directories:

  $ tfm /dir1 /dir2 /dir3 -F=name
  
  # /dir1
  | [f1]      >>/dir2        move to other directory
  | [f2]      >>./dir3       move to subdirectory in current directory
  # /dir2
  | [fA]      >>/dir1/fB     move under changed name
  # /dir3
  {
  | [fa]
  | [fb]
  | [fc]
  }           >>/dir2     move all grouped files
  # /dir4 =   >>/dir2     move all entries under directory but not directory itself
  | [e1]
  | [e2]

Remove selected files:

  $ tfm ./subdir{1,2,3}.ver* -F=name
  
  # subdir1.ver12
  | *[file1]        rm file
  | [file2]
  | [file3]
  | *[file4]        rm file
  # subdir2.ver2
  {
  | [file2]
  | [file3]
  } *               rm group
  | [file4]

Change permissions of multiple files:

  | [file1] :rwx------
  | [file2] :rwxrwx---
  | [file3] :rw-rw-r--

7. Vim integration

This chapter have to be improved. Separate tutorial is needed as well.

Tfman copy vim syntax file and file management plugin into Vim's system directories during installation. Former give you colors during editing of textual representation, later give you ability to manage files through Tfman from within Vim.

Syntax highlighting

To switch on highlighting for particular file, issue following command in Vim:

  :set filetype=tfm

To switch it on automatically, every time you edit file with extension '.tfm', place this in ~/.vimrc file:

  autocmd BufRead,BufNewFile *.tfm set filetype=tfm

File management inside Vim

Small plugin that provides multi-window, textual file management from within Vim. Few commands and mappings for opening scan results in windows, editing textual representation efficiently, parsing content and showing parse results. It even provides commands to perform operations for entry under cursor, similarly to standard file managers. Allows moving up in directories hierarchy and handles simple history of scanned directories, to move back and forward between them.

You can edit textual representation as normal text.

Following commands and mappings are implemented for now:

Scan commands

:Tsr DIR [RECLVL]
scan DIR with recursion level RECLVL (def. 1), send result to Tfman edit window if already exists, open new edit window otherwise
:Tsa, :Tsw, :Tst
same but respectively result is: appended, in new window, in new tab
:To [LNUM]
scan entry at line LNUM (def. line under cursor)
:Toa, :Tow, :Tot
same but respectively result is: appended, in new window, in new tab
:Tpar [LVLS]
scan parent directory, go LVLS up in hierarchy of directories (def. 1)
:Tpara, :Tparw, :Tpart
same but respectively result is: appended, in new window, in new tab

History commands

History, with numbers and directory names is printed at top of every scan result.

:Tr
rescan current directory
:Th NUM
scan directory number NUM in history
:Tn, :Tp
next / previous directory in history
:The
show history

Parse commands

:T
parse textual representation, if not in Tfman edit window go to first edit window in tab
:Tsh
parse and show operations for textual representation only, do not perform them

Edit commands

:Temv [N] [M]
insert move operation in current line, if N specified do it for N lines, if M specified do it for lines N to M
:Tecp [N] [M]
same as above for copy operation
:Ter [N] [M]
Immediately remove entry

Keys

RETURN
scan under-cursor directory in current tfman window
-
go up in hierarchy of directories
<leader>m
add move operatkon in current line with entry name as target
<leader>c
add copy operation in current line with entry name as target
<leader>r
add remove operation in current line
<leader>[ <leader>] <leader>}
next/previous/refresh directory in history

For more consult manpage.

Tfm utility

This short script combines both Tfman modes. It scans given directories, opens result in your favorite text editor and parses edited result after you quit. Editor is determined from EDITOR environment variable. First arguments are directories to scan, rest is passed to Tfman as options. For example:

  $ tfm dir1 dir2 dir3 -R -F=name,type --show-only

8. How Tfman works?

Textual representation

Format of textual representation is strict and consist of entry lines. Each entry line describes one file - its name, type, properties etc. Lines are marked with line tags determining line type. It can be normal line ("|" or space " "), priority line ("$") or directory ("#"). If first non-blank character in line is not line tag, line is ignored at all.

  # dir
    # subdir
    | entry_name
   entry_name
  line_inactive
  .line_inactive
  *line_inactive
'# DIR'
directory, DIR is prepended as parent directory to each entry below, indentation determines its place in directories hierarchy
'| ENTRY'
make entry active, it will be parsed for actions
'$ ENTRY'
make entry active, it will be parsed for actions, all actions in this line are priority actions
'{' '}'
group lines to define actions to all of them at once, do not include directories and subdirectories
'{{' '}}'
group lines to define actions to all of them at once, include directories and subdirectories
'END'
end parsing now

Any other character placed as first non-blank character will cause line to be ignored by parser.

Indentation

Indentation doesn't matter for entries ('|', '$'), because they always belong to directory above them. Indentation matters only for directories ('#'). It determines its parent.

If it is not indented it is new parent directory. If it is on same indentation level as previous directory, it becomes child of its parent. If it is more indented, it becomes child of previous directory. If it is less indented, it becomes child of previous directory's ancestor, as many levels back in hierarchy as many levels of indentation it is shifted left.

  # Dir
    # SubdirA         becomes...>    Dir/SubdirA
    # SubdirB         becomes...>    Dir/SubdirB
  
  # Dir2
    # SubdirA       becomes...> Dir2/SubdirA
      # SubdirB     becomes...> Dir2/SubdirA/SubdirB
    # SubdirC       becomes...> Dir2/SubdirC

Understanding indentation may be a bit tricky. Just stick to following rules:

Entry names and action operands

Composition of entry names and action tag's operands can be done in three ways: absolute name, name relative to execution directory, name relative to in-text parent directory. Entry names and operands being file names have parent directory prepended before actions take place, except if name is absolute path or begins with './'. Note that './' alone is exception to this exception - it has directory prepended.

  # Dir
   name >>/target      virtually becomes:  Dir/name >>/target
   ./name >>./target   virtually becomes:  /home/uli/name >>/home/uli/target
    # Subdir
    /name >>./         virtually becomes:  /name >>Dir/Subdir/./

In above example Tfman was invoked in '/home/uli' directory.

Why is it designed this way? Because somebody may want to operate on names relatively to program execution (current working) directory or operate on absolute names, avoiding prepending parent directory.

Brace expansion and globbing

Brace expansion and globbing is available for entry names. Procedure of preparing entry name is as follows: first braces are expanded, then subdirectory and directory names are prepended if required, then resulted names are subject to globbing.

Brace expansion is performed on entry names similarly to normal shell brace expansion:

  | [name{ab,cd}]     will expand to nameab namecd

Globbing is performed on entry names in the same way as normal shell globbing:

  | [file*]     will expand to all filenames starting with word file''

Brace expansion has precedence over globbing:

  | [file{1,2}*]   will expand to file1* file2*, then globbing occurs

Operands undergo only prepending of directories, they are not subject to brace expansion or globbing.

  # dir
  | [*file{A,B}] >>target

Results in 'dir/*fileA' and 'dir/*fileB' being globbed and then resulting files are moved to 'dir/target'.

Grouping

Group entries to define actions for all of them at once:

  {
  # Dir                   it is not moved because single brackets do not include directories
  | [e1]
  | [e2] >>dir2           individual action has precedece over group actions
  | [e3]
  } >>dir1

It will move all entries to dir1, except e2 which will be moved to dir2. As you can see, actions specified for individual entries have precedence over actions specified for group.

But what if directory needs to be included in group? Use strong grouping:

  {{
  # Dir
  | [e1]
  | [e2]
  | [e3]
  }} nuno:

It will change user for directory and all entries.

Directories and their content

  # Dir oli:
  | entry

Above will change user for directory only.

  # Dir = oli:
  | entry

Above will change user for directory content (entries given below), not for directory itself. We have used '=' tag for that. It changes behavior of all following actions.

  # Dir == oli:
  | entry

Above will change user for directory and for its content. We have used '.=' tag for that. It changes behavior of all following actions.