#!/usr/bin/perl $CopyrightString = <<"EOCPY"; figed (v0.2): a batch and command-line fig file editor Copyright (C) 2006: Kofi A. Laing This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License v2 for more details. You will not receive a copy of the GNU General Public License along with this program; instead, please look it up at: http://www.gnu.org/licenses/gpl.txt EOCPY ######################################################################## $LongHelpString = <<"EOHELP"; A FIRST WARNING First, please do not use this program until you have read all of this help file. Second, if you decide to use the program, please backup all your original drawings and keep a safe copy of those somewhere. Then use figed to play with your drawings. If you dont like what figed does, you can undo it all in one go by restoring your backups. eg. mkdir myfigbaks cp *.fig myfigbaks figed ... (see below for correct usage) then if you dont like what figed did, you can say cp myfigbaks/*.fig . and be none the worse for it. This program is free, and is not guaranteed to be useful for any purpose, use it at your own risk. WHAT IS FIGED? This is a batch and command-line fig file editor. It is able to do simple search and replace transformations on a fig file based on some of the attributes of the drawn objects (other than the values of text fields), as well as some other simple transformations. A particular attribute like the color of an object is encoded with different number codes. Suppose you wanted to change all the green objects in your drawing to blue. In xfig, you would use the "Update" button. If you could select the green objects easily because they are in a group, or because they are on the same depth, then it would be easy. But if the green objects are really scattered and mixed in with other objects at the same depth and in the same group, then you would have to touch each of the green objects in order to change their color. What if you could just indicate that you want the green objects changed to blue? A transformation-command is a command you can give to figed which will consistently change the values of some of the objects in a drawing. Consider the following script, which contains a sample of some transformation-commands: addcolor 32 #cf8600 addcolor 33 #006868 if color=16 sub color=15 if fill=16 sub fill=15 if color=2 sub color=12 if fill=2 sub fill=12 if color=31 sub color=32 if fill=31 sub fill=32 if color=14 sub color=12 if fill=14 sub fill=12 if thickness=2 sub thickness=3 if thickness=1 sub thickness=2 This defines some new colors, does some color substitutions, and thickens lines, all the sorts of things you would like to do to make your presentation show up better on a projector. The numbers in the script encode colors, textures and other attributes of xfig drawing objects. The meaning of the preceding script should be clear -- if not, read the FIG file format definition and then come back to this. "sub" is a command to substitute, and "addcolor" means to create the following user-defined color with the specified value (it will be listed in the file exactly once with the new value, whether or not it already existed with an old value). This can be used to introduce new colors as well as to change the color value of previous color assignments. WHY IS IT USEFUL? I had a lot of fig files for a talk. Then I decided I had to change the background and colorscheme of the whole talk. I had to switch from using bright foregrounds on a dark background to using dark foregrounds on a white background. I wanted a simple command or tool that would do all my xfig file color changes for me consistently (in my drawings the colors have meanings) quite easily. Something that would search for all blue elements and change them to green, say. Or make all my cyan items a little darker so it shows up better on the projector. Or change all items at depth 4 into items at depth 7. All of these are possible but take many mouseclicks in xfig. The more detailed and disorganized the drawings, the more painful. Moreover, using a mouse and manually selecting drawing objects may not be the easiest approach for large numbers of files. Hence this following tool. HOW DO I USE IT? You invoke it by saying any of the following $figed myfile.fig $figed command 'a figed command' myfile1.fig myfile2.fig myfile3.fig ... $figed script myfile1.fig myfile2.fig myfile3.fig ... $figed restrict $figed findalldepths myfile.fig Hint: in place of the list of fig files I often simply say *.fig, to apply commands and scripts to all my drawings. The first form is for interactive use. It presents a sort of shell where you can enter these commands one at a time, while looking at the intermediate result of every transformation step. After each change, the interactive mode will open up the most recent modified copy in an xfig window. It features an infinite undo history (and wastes a lot of memory, but Im assuming you dont care), and keeps a complete record of every editing session. The second form with 'command' runs just a single command, on each of the listed files. The command would be similar to one of the transformation-commands listed in the sample script above. The third form assumes that a number of transformation commands have been saved in a file (such as the entire script above). Then by calling using the third form, you can run the whole script on each of the listed fig files. The fourth one does for xfig diagrams what "grep" does for text files. With this, you can select only some of the depths of a figure to create a new figure. It does not use grep internally, because it knows to use only the numeric forms of depths. Also, for compatibility with Ppower4 and similar programs, when depths are clustered together (differ by one), they are treated as the same layer of an incremental diagram. Thus when you grep for an 88, if there is an 88 an 89 and a 90, then they will all be found. A 95 will not be found if a 94 does not exist. This is useful for creating interactive figures which display more layers as time goes on. The last one just finds all the distinct depth values used in a fig file and writes them to stdout. You will probably not find this useful. It is used by another computer program. WHAT ARE THE SHELL COMMANDS? So obviously you can open figed and obtain a shell, and run transformation-commands interactively. There are other commands which do not change the contents of a fig file, but which are essential for using the command line for this sort of work. I call these "shell commands". They are listed below, with their meanings: COMMAND: quit COMMAND: exit Close the program COMMAND: help Open this file. COMMAND: load start working on a new file. COMMAND: save COMMAND: save save your most recent file looked at, in a filename. COMMAND: history COMMAND: history all This program does not ever delete anything. It keeps all intermediate copies of the file for every sequence of commands, in a tree. Example, you can change aaa to bbb, then change bbb to ccc, then change ccc to ddd. Afterwards you decide you shouldnt have done that, and that you should change aaa to eee to fff instead. Simply say "history", to find out the number of the last one you want to modify (or look at the many xfig windows open on your desk for the closest to what you want to do next), then say "pick 0", to move up the chain of parents to some point. Then after that any edits you apply will work off of that. To undo the last command, simply say "parent". Your entire working history is available until you decide to start working on a new xfig file off the disk. "history" shows you the chain of heritage to the starting file, from any current node. If you run "pick", and say history again, you may get a new chain. COMMAND: read shows you the text file which is the fig file, but for the most recent edited form. COMMAND: view shows you the fig file graphically using xfig, for the most recent edited form. COMMAND: pick explained above, with history COMMAND: parent COMMAND: undo COMMAND: copyright WHAT ARE THE TRANSFORMATION COMMANDS? These are commands that affect the contents of a fig file which has been loaded into memory. They do not change anything on disk unless one of the shell commands above is used. COMMAND: addcolor EXAMPLE: addcolor 33 #ff0000 This sets color 33 to have the value pure red. Read the FIG file format and also use xfigs color selection popup to understand what the hexadecimal means. You could use the color selection popup to play with the sliders till the color looks right, then read off the corresponding hexadecimal number and type it into your command or your script. COMMAND: if sub A is a list of bindings of the form name=value name=value name=value ... EXAMPLE: if color=2 fill=2 depth=5 sub color=12 fill=12 This changes all the light green items which occur at depth 5, into dark green items. In the file format of xfig, 2 means light green and 12 means dark green. The meaning is obvious. If there is more than one field specification in a binding_list, in an if-condition, they must all be satisfied before the substitution will be done. The assumption is that a conjunction is required between the given bindings. There is currently no way to say "OR" in a set of bindings. The list of values that are substituted need not be the same as the ones that were tested. There may be full, partial or no overlap. For objects of type 1,2,3 and 5, the possible names you can use in bindings are as follows --- the nickname is the one-word name you use in figed. The fullname is the equivalent name in the FIG file format description. Datatype says what sort of value you should assign to the chosen field (or test for in the chosen field): NICKNAME FULLNAME DATATYPE object object_code int type sub_type int style line_style int thickness line_thickness int color pen_color int fill fill_color int depth int pen pen_style int area area_fill int For objects of type 4 (Text objects) the possible names are: NICKNAME FULLNAME DATATYPE object object_code int type sub_type int color pen_color int depth int pen pen_style int font int size font_size float angle float flags font_flags int height float length float x int y int string char[] COMMAND: redepth EXAMPLE: redepth 10 This renumbers all the depths in your drawing for you, assuming that you want to keep consecutive depths consecutive, but otherwise you want to introduce a minimum gap of between nonconsecutive depths. This is useful when you need more depths in a region where you have clustered too many depths too close together. It comes in handy for complicated drawings which will be separated into many layers as ppower4 does. COMMAND: restrict EXAMPLE: redepth 10 30 55 This selects the mentioned depths from the figure, together with any other depths that occur contiguously with these. It is useful for picking out the parts of complicated drawings which will be separated into many layers as ppower4 does. COMMAND: tsub searchstring replacestring EXAMPLE: tsub aaa bbb Is just for testing this program. All the shell commands described below apply to any file, including a plain text file. Only the transformation-commands require that the given file be a fig file, which is a particular type of text file. So while testing, you can use this to do simple text tranformations on ordinary text files. Of course you can also use this on fig files, and if you understand the FIG file format, you might actually still be able to use your files as FIG files afterwards! This is for the programmers use only, it isnt intended to be useful to you! I only document it because I refuse to take it out for now. DEVELOPMENT NOTES --- THINGS TO DO, KNOWN BUGS, ETC. * This can read strings and floats and pass them through untouched, but you can not query for strings and floats yet. My eventual goal is to make sure you can query for strings in here, using perl regular expressions, so that any thing you can do like s///g; in perl, can also be done from this command line interface, on all the strings in an xfig file. The problem is that the strings on the command line will have to be written as string='some string' and I can no longer split the bindings based on spaces. Should I run a search which takes each string out and replaces it with a short hash? Then I can put it back into the binding after I have split on space. Work on all that later. For now just color and depth changes and such, just integer changes. * Knowing the color names is not completely trivial. Maybe build in a color chart (an xfig file) which can be used from xfig. It should show the colors *and* indicate the numbers. (Xfig doesnt do that because it is not anticipated that the user will need the actual numbers. * Write the help file properly. * Later on make it capable of taking scripts with comments in them. * Having a way to indicate logical conditions on bindings. Requires simple Context Free tools. EOHELP $UseReadLine = 1; if ($UseReadLine) { use Term::ReadLine; $term = new Term::ReadLine 'niet'; my $attribs = $term->Attribs; $attribs->{rl_completion_entry_function} = $attribs->{'list_completion_function'}; $OUT = $term->OUT || STDOUT; } sub savefile { local($Where, $What) = @_; if ($Interactive) { print "Saving into $Where\n"; } open(FILE, "> $Where"); print FILE $What; close FILE; } sub myreadline { my($ttprompt) = @_; my($TTAns); if ($UseReadLine) { $TTAns = $term->readline($ttprompt); } else { print $ttprompt; $TTAns = ; chop $TTAns; } return $TTAns; } sub viewcurrent { if ($Interactive) { system "xfig figed.$BaseFileName.$CurrentIndex &"; } } sub initializefile { local($GivenFileName) = @_; if ($Interactive) { print "Loading $GivenFileName\n"; } open(FILE, "< $GivenFileName"); $FileString = join('', ); close FILE; @FileStrings = ($FileString); @FileParents = (-1); @FileCommandHistory = (''); $CurrentIndex = 0; if ($Interactive) { &savefile("figed.$BaseFileName.$CurrentIndex", $FileString); } &viewcurrent(); # has its own protection. } sub printhistory { local(@ArrVals) = @_; open( FILE, "| less"); foreach $Idx (@ArrVals) { print FILE sprintf("%4d %4d %s\n", $Idx, $FileParents[$Idx], $FileCommandHistory[$Idx]); } close FILE; } sub createnewentry { # if you want to remove the space-wasteful infinite # history, you may be able to do it all in here by making this whole # block conditional on $Interactive. If youre not interactive, there # isn't going to be any undoing, so there is no need for the stack. # For now, I laze. What's a little space wastage? local($NewFileString) = @_; if ($NewFileString ne '') { push(@FileStrings, $NewFileString); push(@FileParents, $CurrentIndex); push(@FileCommandHistory, $Command); $CurrentIndex = $#FileStrings; if ($Interactive) { &savefile("figed.$BaseFileName.$CurrentIndex", $NewFileString); } } } sub readcurrent { open( FILE, "| less"); print FILE $FileStrings[$CurrentIndex]; close FILE; } sub processoneline { # make sure IfBindings, SubBindings and NewDepths # are not changed local(*Vals) = @_; local($Satisfied); # for finding all the distinct depths present -- we do it always but # only return the result if the mode is right. $DepthList{$Vals{'depth'}} = 1; if ($Mode eq 'grep_depths') { if ($NewDepths{$Vals{'depth'}} > 0) { $PassThru = 1; # this line item goes through. } else { $PassThru = 0; # if mode is not grep_depths then # passthru is never set to zero } } elsif ($Mode eq 'replace_many_fields') { # first check if all the ifbindings are satisfied. $Satisfied = 1; # satisfied by default foreach $Key (keys %IfBindings) { if ($Vals{$Key} != $IfBindings{$Key}) { $Satisfied = 0; # at least one difference found } } # then modify all the subbindings if ($Satisfied) { # no differences found # print "Match!: $OneLine\n"; #debug foreach $Key (keys %SubBindings) { $Vals{$Key} = $SubBindings{$Key}; } } } elsif ($Mode eq 'replace_many_depths') { # print 'replacing ', $Vals{'depth'}, ' with ', $NewDepths{$Vals{'depth'}}; #debug $Vals{'depth'} = $NewDepths{$Vals{'depth'}}; # print 'assignedval= ', $Vals{'depth'}, "\n"; #debug } } sub scanmodfile { # make sure currentindex, IfBindings, SubBindings and NewDepths # are not changed local($Mode, $OldFileString, *IfBindings, *SubBindings, *NewDepths) = @_; local($NewFileString) = ''; local(@OldArr) = split(/\n/, $OldFileString); local($InBody) = 0; local(%DepthList) = (); local($PassThru) = 1; while($OneLine = shift @OldArr) { if ($InBody) { # do the interesting substitutions if ($OneLine =~ /^(-?6)/) { # this is a group line $PassThru = 1; # group lines always go through. # no need to condition this on grep_depths because # it is the default behavior for other modes. } if ($OneLine =~ /^ (1|2|3|5)\s # object (-?[0-9]+)\s # type (-?[0-9]+)\s # style (-?[0-9]+)\s # thickness (-?[0-9]+)\s # color (-?[0-9]+)\s # fill (-?[0-9]+)\s # depth (-?[0-9]+)\s # pen (-?[0-9]+)\s # area (.*) $/x) { %Vals = (); # first note first nine fields in object line. $Vals{'object'} = $1; $Vals{'type'} = $2; $Vals{'style'} = $3; $Vals{'thickness'} = $4; $Vals{'color'} = $5; $Vals{'fill'} = $6; $Vals{'depth'} = $7; $Vals{'pen'} = $8; $Vals{'area'} = $9; $Rest = $10; &processoneline(*Vals); # and reconstruct $OneLine = join(' ', $Vals{'object'}, $Vals{'type'}, $Vals{'style'}, $Vals{'thickness'}, $Vals{'color'}, $Vals{'fill'}, $Vals{'depth'}, $Vals{'pen'}, $Vals{'area'}, $Rest); } if ($OneLine =~ /^ (4)\s+ # object (-?[0-9]+)\s+ # type (-?[0-9]+)\s+ # color (-?[0-9]+)\s+ # depth (-?[0-9]+)\s+ # pen (-?[0-9]+)\s+ # font (-?[0-9]+(\.[0-9]+)?)\s+ # size (-?[0-9]+(\.[0-9]+)?)\s+ # angle (-?[0-9]+)\s+ # flags (-?[0-9]+(\.[0-9]+)?)\s+ # height (-?[0-9]+(\.[0-9]+)?)\s+ # length (-?[0-9]+)\s+ # x (-?[0-9]+)\ # y (.*)\\001 # string $/x) { %Vals = (); # first note first nine fields in object line. $Vals{'object'} = $1; $Vals{'type'} = $2; $Vals{'color'} = $3; $Vals{'depth'} = $4; $Vals{'pen'} = $5; $Vals{'font'} = $6; $Vals{'size'} = $7; $Vals{'angle'} = $9; $Vals{'flags'} = $11; $Vals{'height'} = $12; $Vals{'length'} = $14; $Vals{'x'} = $16; $Vals{'y'} = $17; $Vals{'string'} = $18; &processoneline(*Vals); # and reconstruct $OneLine = join('', $Vals{'object'}, ' ', $Vals{'type'}, ' ', $Vals{'color'}, ' ', $Vals{'depth'}, ' ', $Vals{'pen'}, ' ', $Vals{'font'}, ' ', $Vals{'size'}, ' ', $Vals{'angle'}, ' ', $Vals{'flags'}, ' ', $Vals{'height'}, ' ', $Vals{'length'}, ' ', $Vals{'x'}, ' ', $Vals{'y'}, ' ', $Vals{'string'}, '\001' ); } # we do not explicitly check for piggy lines. Instead we assume # that anything that does not begin immediately with a 1,2,3,4,5 #, or -?6, is a piggy. Piggies do not have depth, and do not # require PassThru turned off or on. They simply inherit the # PassThru status of the previous nonpiggy. Piggies never follow # a -?6 so no problem there. } elsif ($OneLine eq "1200 2") { $InBody = 1; } if ($PassThru) { $NewFileString .= $OneLine."\n"; } } if ($Mode eq 'replace_many_fields' || $Mode eq 'grep_depths' || $Mode eq 'replace_many_depths') { return $NewFileString; } elsif ($Mode eq 'find_all_depths') { return sort { $a <=> $b } keys %DepthList; } else { die "Unknown mode passed to scanmodfile\n"; } } sub findalldepths { local(%IfBindings) = (); # this is a dummy for the following call. local(%SubBindings) = (); # this is a dummy for the following call. local(%NewDepths) = (); return &scanmodfile('find_all_depths', $FileStrings[$CurrentIndex], *IfBindings, *SubBindings, *NewDepths ); } sub printnewdepths { my($Str) = @_; foreach $key (sort { $a <=> $b } keys %NewDepths) { print $Str, ": ", $key, ' --> ', $NewDepths{$key}, "\n"; } } sub createnewdepthbindings { ($Interval, @NewDepthList) = @_; my $Idx; if ($Interval < 2) { die "The interval passed is too small.\n"; } $Equivalent = 2*$Interval; # arbitrary lower limit on depths. # which gives some space up top for more additions. quite arbitrary. foreach $Idx (0..$#NewDepthList) { $NewDepths{$NewDepthList[$Idx]} = $Equivalent; if ($Idx < $#NewDepthList) { if ($NewDepthList[$Idx]+1 == $NewDepthList[$Idx+1]) { $Equivalent++; } else { # create a minimum length gap of $Interval # then round up to next multiple of interval for nice numbers $Equivalent += 2*$Interval-1; $Equivalent = $Interval * int($Equivalent / $Interval); } } } # &printnewdepths("createnewbindings"); return *NewDepths; } sub createdepthswitches { (*NewDepthList, *DepthsToKeep) = @_; my $Idx; my(%DepthLeaders) = (); my(%ChosenLeaders) = (); # print 'given depths = ', join(' ', @NewDepthList), "\n"; # print 'depths to keep = ', join('><', @DepthsToKeep), "\n"; $Leader = $NewDepthList[0]; foreach $Idx (0..$#NewDepthList) { $DepthLeaders{$NewDepthList[$Idx]} = $Leader; if ($Idx < $#NewDepthList) { unless ($NewDepthList[$Idx]+1 == $NewDepthList[$Idx+1]) { $Leader = $NewDepthList[$Idx+1]; } } } %ChosenLeaders = (); foreach $Depth (@DepthsToKeep) { if (defined $DepthLeaders{$Depth}) { # means it is present in diagram $ChosenLeaders{$DepthLeaders{$Depth}} = 1; } } # print 'chosen leaders = ', join(' ', sort {$a <=> $b} keys %ChosenLeaders), "\n"; %NewDepths = (); foreach $Depth (@NewDepthList) { if (defined $ChosenLeaders{$DepthLeaders{$Depth}}) { # my leader is a chosen leader $NewDepths{$Depth} = $Depth; # depth's newdepth gets to be itself, # and it will survive through the restrict. } else { # depth's newdepth is null, and the depth won't survive. } } # print 'selected depths = ', join(' ', sort {$a <=> $b} keys %NewDepths), "\n"; return *NewDepths; } ######################################################################## sub runcommand { local($Command) = @_; if ($Command =~ /^addcolor\s+(\d+)\s+(\#[0-9a-f]{6})$/) { $ColorNum = $1; $ColorHex = $2; if ($ColorNum >= 32) { # the first 0-31 are xfig's inbuilt colors # they can't be redefined. $NewFileString = $FileStrings[$CurrentIndex]; $NewFileString =~ s/\n0\s+$ColorNum\s+\#[0-9a-f]{6}\n/\n/; $NewFileString =~ s/\n1200 2\n/\n1200 2\n0 $ColorNum $ColorHex\n/; &createnewentry($NewFileString); &viewcurrent(); } } elsif ($Command =~ /^if((\ (object| type| style| thickness| color| fill| depth| pen| font| size| angle| flags| height| length| x| y| area) =(-?[0-9]+))*)\s* sub((\ (object| type| style| thickness| color| fill| depth| pen| font| size| angle| flags| height| length| x| y| area) =(-?[0-9]+))*)\s*$/x) { $IfBindingList = $1; $SubBindingList = $5; %IfBindings = (); @IfBindings = grep(/\S/, split(/\s+/, $IfBindingList)); foreach $Binding (@IfBindings) { if ($Binding =~ /([a-z_]+)=(-?[0-9]+)/) { $IfBindings{$1} = $2; } else { die "unrecognized binding $Binding\n"; } } %SubBindings = (); @SubBindings = grep(/\S/, split(/\s+/, $SubBindingList)); foreach $Binding (@SubBindings) { if ($Binding =~ /([a-z_]+)=(-?[0-9]+)/) { $SubBindings{$1} = $2; } else { die "unrecognized binding $Binding\n"; } } %NewDepths = (); # this is a dummy for the following call. $NewFileString = &scanmodfile('replace_many_fields', $FileStrings[$CurrentIndex], *IfBindings, *SubBindings, *NewDepths ); &createnewentry($NewFileString); &viewcurrent(); } elsif ($Command =~ /^redepth ([0-9]+)$/) { $Interval = $1; @NewDepthList = &findalldepths(); %IfBindings = (); # this is a dummy for the following call. %SubBindings = (); # this is a dummy for the following call. # this array is now computed as a function of @NewDepthList; %NewDepths = (); # this is to receive values for following call. *NewDepths = &createnewdepthbindings($Interval, @NewDepthList); # &printnewdepths("redepth"); $NewFileString = &scanmodfile('replace_many_depths', $FileStrings[$CurrentIndex], *IfBindings, *SubBindings, *NewDepths ); &createnewentry($NewFileString); &viewcurrent(); } elsif ($Command =~ /^restrict(( [0-9]+)+)\s*$/) { @DepthsToKeep = grep(/^\d+$/, split(/\s+/, $1)); # discard nonnums @NewDepthList = &findalldepths(); %IfBindings = (); # this is a dummy for the following call. %SubBindings = (); # this is a dummy for the following call. # this array is now computed as a function of @NewDepthList; %NewDepths = (); # this is to receive values for following call. *NewDepths = &createdepthswitches(*NewDepthList, *DepthsToKeep); #todo implmenet $NewFileString = &scanmodfile('grep_depths', # todo implement $FileStrings[$CurrentIndex], *IfBindings, *SubBindings, *NewDepths ); &createnewentry($NewFileString); &viewcurrent(); } elsif ($Command =~ /\S/) { print "Command Not recognized: $Command\n"; } } sub commandloop { while (1) { print "Current Index = $CurrentIndex\n"; if (defined ($Command = &myreadline($ttprompt))) { $NewFileString = ''; # to check whether one of any number of pathways # generates a new string for the data structure stack. ## Transparent commands that don't change basic data structures if ($Command =~ /^(quit|exit)$/) { last; } elsif ($Command =~ /^$/) { # do nothing } elsif ($Command =~ /^clx$/) { # clean up the windows of temp displays system "killall xfig"; } elsif ($Command =~ /^help$/) { open( FILE, "| less"); print FILE $LongHelpString; close FILE; } elsif ($Command =~ /^copyright$/) { open( FILE, "| less"); print FILE $CopyrightString; close FILE; ## File management commands } elsif ($Command =~ /^load (\S+)$/) { $BaseFileName = $1; &initializefile($BaseFileName); # start over with a new file. } elsif ($Command =~ /^save( (\S+))?$/) { # is saved into a file without clearing the modification stack. if ($2 ne $FileName) { &savefile($2, $FileStrings[$CurrentIndex]); # where, what } else { &savefile($FileName, $FileStrings[$CurrentIndex]); } } elsif ($Command =~ /^history$/) { @ArrVals = (); $Idx = $CurrentIndex; while ($Idx != -1) { push(@ArrVals, $Idx); $Idx = $FileParents[$Idx]; } @ArrVals = reverse(@ArrVals); &printhistory(@ArrVals); } elsif ($Command =~ /^history all$/) { @ArrVals = (0..$#FileStrings); &printhistory(@ArrVals); } elsif ($Command =~ /^read$/) { &readcurrent(); } elsif ($Command =~ /^view$/) { &viewcurrent(); } elsif ($Command =~ /^pick ([0-9]+)$/) { if (0 <= $1 && $1 <= $#FileStrings) { $CurrentIndex = $1; } else { print "There is no such array item: $1\n"; } } elsif ($Command =~ /^(parent|undo)$/) { # just moves pointer up the tree if ($CurrentIndex != 0) { $CurrentIndex = $FileParents[$CurrentIndex]; } # Nontrivial commands grow the data structure history stack. # the first is just for testing so that we can test on plain # text files. } elsif ($Command =~ /^tsub (\w+) (\w+)$/) { $NewFileString = $FileStrings[$CurrentIndex]; $Search = $1; $Replace = $2; $NewFileString =~ s/$Search/$Replace/ge; &createnewentry($NewFileString); # the next two are specific to xfig editing, as is the viewcurrent command. } else { &runcommand($Command); } } else { print "Undefined command argument\n"; } } } $prompt = "\nfiged> "; $WhatToDo = shift @ARGV; unless ($WhatToDo) { # what happens when this is called without an argument. open( FILE, "| less"); print FILE $LongHelpString; close FILE; exit(0); } if ($WhatToDo eq 'findalldepths') { $Interactive = 0; print join(' ', &findalldepths()), "\n"; } elsif ($WhatToDo =~ /restrict/) { $BaseFileName = shift @ARGV; $DestFileName = shift @ARGV; @DepthsToKeep = @ARGV; &initializefile($BaseFileName); &runcommand('restrict '.join(' ', @DepthsToKeep)); ### scanmodfile needs to be called here for restrict to work. &savefile($DestFileName, $FileStrings[$CurrentIndex]); } elsif ($WhatToDo =~ /(command|script)/) { $Interactive = 0; if ($WhatToDo eq 'command') { $ScriptFile = shift @ARGV; } else { $ScriptFileName = shift @ARGV; open(FILE, "< $ScriptFileName"); $ScriptFile = join('', ); close FILE; } @FileArray = @ARGV; foreach $BaseFileName (@FileArray) { &initializefile($BaseFileName); foreach $Command (grep(/\S/, split(/\n/, $ScriptFile))) { &runcommand($Command); } &savefile($BaseFileName, $FileStrings[$CurrentIndex]); } } else { $Interactive = 1; $BaseFileName = $WhatToDo; print <<'EOCOPYRIGHT'; figed version 0.1, Copyright (C) 2006 Kofi A. Laing figed comes with ABSOLUTELY NO WARRANTY; for details type `copyright'. This is free software, and you are welcome to redistribute it under certain conditions; type `copyright' for details. Type 'help' if you've never used this program before. EOCOPYRIGHT if (-f $BaseFileName) { &initializefile($BaseFileName); &commandloop(); } else { die "File $BaseFileName does not exist.\n"; } }