My Clipper packaging script for Puppy

Under development: PCMCIA, wireless, etc.
Post Reply
Message
Author
User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

My Clipper packaging script for Puppy

#1 Post by Sit Heel Speak »

(***EDITED Nov. 15 2010: to make a long story short: my Clipper script produces a bash script which fills the build-source-dir for an .sfs or .pet add-in package for Puppy Linux. An example of the bash script it produces, to build an .sfs for Opera 11 alpha, is below in this post; to download the Clipper script itself, go to this post.*** Or read on, to be entertained and to become highly informed...)

Hi y'all,

At the outset, I intend the purpose of this thread to be two-fold:

One, to disclose a very powerful application (and building-block) packaging aid for Puppy Linux, a script I have developed in the Clipper Summer '87 database management system programming language. I am not yet fluent in bash, so I wrote it in the language I am fluent in. Using this script, I was able to produce an .sfs for Opera 11 alpha in under one hour.

And two, to ask for the aid of someone who is as competent with bash (and maybe xHarbour) as I am with Clipper.

If there already exists a pure Linux tool which accomplishes the same purpose, I would like to know what it is, and I will learn that I have been using a wood-chipper to shave with, and this thread will end quickly.

Or, if no such tool exists, let's work together to convert my Clipper script into a purely Linux form which unlike Clipper is not dependent on DOS and Windows.

The Clipper script takes as input two directory listings of the entire Puppy distro tree, taken before-and-after, and gives as output a bash script.

The resulting bash script copies from the tree, into a destination-subdirectory on a separate partition, all files which are present in the newer version of the tree but were not present in the older. Optionally, the bash script can also copy files which are present in both the new and the old tree but differ in size or timestamp.

The destination-subdirectory which all new files in the distro are copied into by the bash script can then serve as the build-source-directory for creation of a squashfile package using mksquashfs, or a PET package using dir2pet.

Thus, the procedure for using it is as follows:

(***slightly edited Nov 12 2010, dBase III+ is no longer required***)

1. Take a "before" directory tree listing.
2. Install the application and adjust or extend it; or, compile the utility together with its dependencies.
3. Take an "after" directory tree listing. Adjust the line-endings from LF to CRLF using Geany.
4. Transfer the "before" and "after" listings to a DOS+Windows 98SE machine.
5. Run the Clipper script to produce the bash script.
6. (no longer necessary)
7. Transfer the bash script to the Linux machine. Adjust its line-endings from CRLF to LF using Geany, and make it executeable.
8. Run the bash script to fill the build-source-directory.
9. Adjust any .ini files et cetera in the build-source-directory for the first-run scenario, and/or add the optional pinstall.sh and puninstall.sh scripts if building a PET package.
10. Run mksquashfs or dir2pet.
11. Test.
12. Upload.
13. Announce.
14. Bask in the imaginary adulation of millions.

I have used this procedure to package one low-level building-block whose complexity is too high to wield new2dir plus dir2pet on -namely, gcc, last year-- and one application package for which new2dir is not applicable, because the package is supplied by the vendor not as compileable source code, but rather as precompiled binaries plus an installation script -namely, the Opera web browser, a few days ago.

Even if I do say so myself, both of these are of exceedingly high quality. Mainly because the human error factor of omitting critical components is greatly reduced or eliminated by use of the Clipper script and its resulting bash script.

Now...before I go and make not only a boaster but moreover a colossal fool of myself...

Does there already exist, a Linux utility --or, can a bash script be readily devised, by someone fluent in bash-- which accomplishes the same purpose, of taking two ls listings and copying-in all the new files into a build-sourcedir?

Or, more simply, is it possible, with a single line of bash code, to do the same thing: to copy over, keeping the tree structure intact, just those files which were created after a specific time/date?
Last edited by Sit Heel Speak on Tue 16 Nov 2010, 17:36, edited 7 times in total.

User avatar
shinobar
Posts: 2672
Joined: Thu 28 May 2009, 09:26
Location: Japan
Contact:

Re: My Clipper packaging script for Puppy

#2 Post by shinobar »

Sorry, i cannot clearly image your intention.
  1. Function
    Your system is the same as the new2dir plus dir2pet intends but cover the case the new2dir plus dir2pet miss?
  2. Way
    Your way is like to make a snapshot of the current status under / or under /usr and /root or somewhere,
    and after the instalation completed, take again the snapshot so that we can pick up the new files?
Assume my guess is right, we can check the time stamps of the files.
Orthodox way:

Code: Select all

touch milestone
(something compile and install...)
find /usr /root -newer milestone
EDIT: '-newer' is not right. Use '-cnewer' instead.

But i do somewhat tricky:
Boot up the Puppy with 'pmedia=ataflash' option.
Then the pupsave is mounted on /initrd/pup_ro1.
Puppy makes a ramdisk and mount it on /initrd/pup_rw.
The /initrd/pup_rw holds all files after the boot.

So we reboot with 'pmedia=ataflash', and do compile and install.
We can copy the new files from /initrd/pup_rw.

But it is rare case i need to take this tricky way.
Nor i do not use new2dir.
Normally, next does work: (of course depends on the package)

Code: Select all

make
make DESTDIR=/mnt/home/tmp/some-root install
EDIT: /mnt/home shoud be ext2/3/4 file system. if not, use /var/tmp instead (require the pupsave large enough).

i am afraid i may be misunderstanding your intention... :wink:
Last edited by shinobar on Thu 11 Nov 2010, 01:04, edited 1 time in total.
Downloads for Puppy Linux [url]http://shino.pos.to/linux/downloads.html[/url]

User avatar
shinobar
Posts: 2672
Joined: Thu 28 May 2009, 09:26
Location: Japan
Contact:

Re: My Clipper packaging script for Puppy

#3 Post by shinobar »

Sit Heel Speak wrote:new2dir is not applicable, because the package is supplied by the vendor not as compileable source code, but rather as precompiled binaries plus an installation script
I see. In such a case, some tool instead of new2dir will be helpful.

One of the possible script:

Code: Select all

touch milestone

(... install process...)

find /usr /root /etc -cnewer milestone | xargs -I{} install -D  {} /var/tmp/new-root{}
Just an idea.
Downloads for Puppy Linux [url]http://shino.pos.to/linux/downloads.html[/url]

big_bass
Posts: 1740
Joined: Mon 13 Aug 2007, 12:21

#4 Post by big_bass »

Code: Select all

new2dir is not applicable, because the package is supplied by the vendor not as compileable source code, but rather as precompiled binaries plus an installation script 

as shinobar suggested and I have a script I use for this that takes advantage of built in linux commands using slackware's pkgtool


**forum member amigo (Gilbert Ashley ) src2pkg developer *has spent years on this hope he sees this post



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

now for odd packages that have their own make install
its easy to change the install directory as a new root DESTDIR


http://www.gnu.org/prep/standards/html_ ... STDIR.html

Prepending the variable DESTDIR to each target in this way provides for staged installs, where the installed files are not placed directly into their expected location but are instead copied into a temporary location (DESTDIR). However, installed files maintain their relative directory structure and any embedded file names will not be modified.


heres a good read of options
http://www.dwheeler.com/essays/automating-destdir.html
=====================================

there is a new root option which can install one package or the entire distro !!
into a newroot and have it run thats how I built TXZ_pup

the same code is used to make a SFS its easy and I scripted all the code already to mesh with pkgtool seamlessly even a GUI that lets you select what packages you use





you could also try using src2pkg that will produce perfect packages
most of the time automatically *some special packages need to pass options which is expected


here is a simple line of code to install to a new root
you need pkgtool installed this is built into txz_pup

Code: Select all

cd /root/packages2install
  installpkg -root /squashfs-root *.t?z
explained
1.)cd /root/packages2install ##this folder has one or many packages I want to install

2.) installpkg # thats the official command for package management installs

3. the -root option is a prefix flag that says the path that follows is where you want to install the packages it is the "new root"
in this case when you unsquash a SFS a folder is made called squashfs-root as the default you could rename that to your preference

4.) I used a *.t?Z because I dont want to manually type the whole package name so any package ending with tgz or txz or other compressions work


Joe

User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

Re: My Clipper packaging script for Puppy

#5 Post by Sit Heel Speak »

shinobar wrote:...But i do somewhat tricky:
Boot up the Puppy with 'pmedia=ataflash' option.
Then the pupsave is mounted on /initrd/pup_ro1.
Puppy makes a ramdisk and mount it on /initrd/pup_rw.
The /initrd/pup_rw holds all files after the boot.

So we reboot with 'pmedia=ataflash', and do compile and install.
We can copy the new files from /initrd/pup_rw.
Ah, I see...hehehe , that is SWEET!!!
shinobar wrote:Sorry, i cannot clearly image your intention.
  1. Function
    Your system is the same as the new2dir plus dir2pet intends but cover the case the new2dir plus dir2pet miss?
  2. Way
    Your way is like to make a snapshot of the current status under / or under /usr and /root or somewhere,
    and after the instalation completed, take again the snapshot so that we can pick up the new files?
Yes, exactly right. The Clipper script looks at before-and-after snapshots taken under / and parses each line, then writes the needed mkdir, cd, and cp lines into the bash script. Symlinks are treated as a special case, the bash script explicitly re-creates them using cd then ln -s -T, or ln -s for symlinks to subdirectories.

The new2dir+dir2pet sequence is good for the case when what is being packaged is just a single compiled source. Ah, but...what do you do when you must actually compile a series of source-packages, for example gcc+mpfr+gmp, or that entire devilishly tricky chain from boost through poppler to pango?

(Of course, your pmedia=ataflash method overcomes all that. Pure genius...)
shinobar wrote:...Normally, next does work: (of course depends on the package)

Code: Select all

make
make DESTDIR=/mnt/home/tmp/some-root install
No kidding...I did not know DESTDIR exists :lol:

I tried compiling gcc with --prefix=/mnt/sdb1/sourcedir and found that the entries in /usr/lib/pkgconfig turned out wrong. I presume that using DESTDIR makes them turn out right?

Hoo hee...booting with pmedia=ataflash. And then simply copy the new files from /initrd/pup_rw. Man, that is elegant...whereas what I cobbled together is an elephant :lol:
big_bass wrote:**forum member amigo (Gilbert Ashley ) src2pkg developer *has spent years on this hope he sees this post
Many, many thanks. I must study DESTDIR...

I have PM'ed him to request that he look in here. He may get a chuckle out of it...

Meanwhile, I will study your tools and src2pkg and see whether I have merely invented the horse-drawn zeppelin compared to these...
Last edited by Sit Heel Speak on Sun 14 Nov 2010, 09:42, edited 1 time in total.

User avatar
shinobar
Posts: 2672
Joined: Thu 28 May 2009, 09:26
Location: Japan
Contact:

DESTDIR

#6 Post by shinobar »

Sit Heel Speak wrote:I tried compiling gcc with --prefix=/mnt/sdb1/sourcedir and found that the entries in /usr/lib/pkgconfig turned out wrong. I presume that using DESTDIR makes them turn out right?
Yes. Typically:

Code: Select all

./configure --prefix=/usr
make
make DESTDIR=/mnt/home/tmp/some-root install
Note that it does not always work. Depends on the package.

Another note:
The file system of the DESTDIR should be of Linux.
I have Puppy frugal installed on ext3 partition so that the /mnt/home is ext3.
Or, use some other Linux partitions for working directory.
You can use /var/tmp for small packages. Be aware the size of /var/tmp, is the part of pupsave.
Downloads for Puppy Linux [url]http://shino.pos.to/linux/downloads.html[/url]

amigo
Posts: 2629
Joined: Mon 02 Apr 2007, 06:52

#7 Post by amigo »

SHS, I got your PM. I had already seen this thread but had decided not to post about the functionalities of src2pkg -tooting one's own horn doesn't always set well...

But since you asked, the concept you are working is not new. There packing systems which do about the same thing. The big disadvantage is the amount of space needed to store two copies of the material to be compared. In your case, doing it in a windows-based IDE/language makes things less workable as you can't create the content under Windows.

What you are doing can be done in a few short lines using bash and some CLI utils. The core command could be rsync and/or diff. There are also the 'cmp' and 'comm' commands which could compare the content of directory listings. rsync is probably the best tool for this as it already includes the options to choose files according to modifiation time or size. The 'find' utility does this also, but you'd still need to pipe the output through something which would copy the files.

As I said, there are some packaging systems which do something similar. One creates a complete list of the files on your system first, then lets you create content, then creates a new list of all the files on your system and compares the two lists. You can probably imagine that this takes a long time(it does), and even though it sounds fool-proof, it leaves lots of room for false listings.

Most of you who want to create SFS's for Puppy, want to include various things which are not all related -except that you want them separated. src2pkg is a tool for creating packages -one good package at a time. when used to make slackware-type packages(at least), you can then 'install' the packages to a temporary directories where you have installed other packages or copied 'loose' files. Using a package-based method to create content is very sensible
as it allows for easy repitition *especially* if your package creation is based on using scripts or recipes to build each package. This is the *only* way to accurately and easily repeat your content-creation work -unless you keep very explicit note about what you did to create the content. Even so, a script is much more concise than any prosaic description of what you did -by a factor of 10 or more.

Even though src2pkg cncentrates on building 'one good package' it has always been designed to work with other scripts which use src2pkg to accomplish smaller steps of a bigger whole. Joe has written tools which do just this. Creating a directory, installing some stuff there and then creating an SFS, a mega-package, an *.iso or a compressed 'module' (like for slax or tiny-core) is pretty simple once you have the content packaged.

src2pkg is unique among package-building software in that it can be used without any 'recipe'. Other systems require some sort of recipe for every build: rpm *.spec file, debian/rules, slackware *.SlackBuild script, etc. The actual packaging of content is pretty easy under any system. The hard part is creating and 'conditioning' the content so that it meets the requirement of the target system and installation method.

The first thing that src2pkg concentrates on is (usually) compiling the software. It includes 'knowledged of many, many, methods of configuring and compiling software. The most obvious thing to mention here is that a great deal of sources do *not* support the use of DESTDIR. A system which relies on that is doomed to fail pretty quickly. There are many 'flavors' of DESTDIR(same concept with a different variable name like install_root). src2pkg can detect and use any of about a dozen of these. More importantly, it can 'trace' the creation of content using nearly any command, and can usually even divert the destination of the content. It allows you to build and 'fake_install' content without 'polluting' your system with that content. This is mostly accomplished using a library called 'libsentry' which is a forked and extended installwatch. 'dir2pet' uses an older, limited version of installwatch which can't do these 'diversions'. It is also unable to track many commands which are used to create or *alter* file content which libsentry supports(like chown, chmod, sed).

I'm always interested in packaging discussions, so I could go on and on about all sorts of things you haven't thought of yet... But let's not just now...

My first advice is to dig into bash -Joe has come a long way in a couple of years. Since shell is the native scripting language of linux, there is no lack of material to study. All the events that you have during bootup are controlled with shell script. Since you know well the sequence of the events on your system, you can easily study the scripts which control them. A couple of caveats though, init scripts are pretty long and complex as a starting point -the Puppy init scripts are a nightmare to read... Still, most of the 'programs' posted here by users are shell-based, and are usualy pretty short -especially non-GUI ones. You already know how to program -learning bash will mostly be a matter of learning syntax.

Have a look here:
http://distro.ibiblio.org/pub/linux/dis ... /examples/
for examples of using src2pkg.

This script:
http://distro.ibiblio.org/pub/linux/dis ... GroupBuild
is an example of using a small script to use src2pkg to create several individual packages and install them as it goes. It is useful for creating groups of packages which depend on each other.

I recommend you to read and save a copy of the 'Automating DESTDIR' mentioned by someone else. As you learn more it will become even more useful. What it says about src2pkg is outdated and incomplete, though.

src2pkg can isolate created content in *nearly* every case by using DESTDIR or one of its' variants(-DEST option), re-directing destination paths(-JAIL option), or installing into a union-mounted chroot(-UNION option). There very, very few cases where content must really be installed to your real system in order to achieve the desired result. When this is the case, src2pkg still offers a way to back up any files which are about to be over-written and then restore them after separating the new content into the package-building area(-SAFE option).

src2pkg 'knows' how to configure and build many kinds of sources, including autoconf, cmake, imake, perl(several distinct methods) and python modules, cmake, qmake, jam, scons, waf, tcl and more.

User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

posted: my Clipper Summer '87 packaging tool

#8 Post by Sit Heel Speak »

amigo wrote:...tooting one's own horn doesn't always set well...
--how well I know it--

I did not expect that my method was totally new; this in-depth education from shinobar and Joe and you is the wonderful smorgasbord of knowledge I was hoping to see.

Question:

Do any of the above-described, already-existing methods, give you a source-directory-building bash script to look through, so as to provide easy control of the package's contents at the level of the individual files --so you don't include, for example, your own /root/.history in the package?

In case there are any of my fellow Clipper baiji still roaming this planet, who might find this useful...I will go ahead and post it, below, so that anyone who can draw it from the stone (of its being cast in Clipper) gets to use it.

It is my hope that some such Mighty One will convert it to a pure Linux form and share it in that form with us mere mortals. It would be nice if it could have a gtkdialog3 interface with which to choose the filters :)

PM me, O Worthy Aspirant, if any of my Clipper code is unclear. I have tried to comment it much more liberally than Clipper code usually is!

Presenting...Sit Heel Speak's HORSE-DRAWN ZEPPELIN!!! :lol:

***EDITED Nov. 14 2010: the first two versions had a bug in mk_sfs5.prg, it was not identifying symlink targets correctly, when the symlink is to a directory. Besides, if I'm going to upload free software, I guess I'd better include the gpl with it :shock: ***

***EDITED later Nov. 14: two more bugfixes. Download the revised version 4, 4 message-slots below.***
Last edited by Sit Heel Speak on Sun 14 Nov 2010, 19:05, edited 7 times in total.

User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

#9 Post by Sit Heel Speak »

deleted by poster
Last edited by Sit Heel Speak on Sun 14 Nov 2010, 05:59, edited 1 time in total.

User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

example bash scripts produced by my Clipper packaging script

#10 Post by Sit Heel Speak »

Attached is the bash script, makesfs, for Opera 11 alpha as it comes from the Clipper script, with CRLF line endings. About half of it is unnecessary to include in the package, the half which is under the heading "Old has an older version". Editing out this half, plus /root/.history (line 35) and the few lines near the top having to do with medit (lines 22-26), takes just a few minutes in Geany.

Also attached is makesfs after editing, and after changeing the line endings to LF. This is the actual script by which I produced my Opera 11 alpha .SFS package. ***note: I did, however, cheat a bit, I added some icons before taking the after snapshot, in places that are arcanely specific to Puppy. So, you couldn't just install Opera and run this makesfs and have it turn out perfect. There is still craftsmanship involved in packaging...***

Screen capture showing the DOS window the Clipper script runs in. The total time of 1,074 seconds is on a P3-800. Total time to produce the Opera 11 alpha .sfs, was under one hour.
http://i53.tinypic.com/2cxgmtl.png

To download the Clipper script itself, go two message slots below this one, here.
Attachments
makesfs--ready_to_run.gz
makesfs for Opera 11 alpha, after changeing line endings to LF and editing
(5.64 KiB) Downloaded 875 times
makesfs--as_it_comes_from_mk_sfs5.gz
makesfs for Opera 11 alpha, as it comes from the Clipper script, CRLF endings, unedited
(10.88 KiB) Downloaded 877 times
Last edited by Sit Heel Speak on Mon 15 Nov 2010, 23:01, edited 3 times in total.

User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

posted: version 3 of my Clipper packaging script for Puppy

#11 Post by Sit Heel Speak »

Changes from Version 3 to Version 4:

Two bugfixes in mk_sfs5.prg:

Line 420: "public time1" is not needed (and so is commented-out)

Line 350, change from

toseek=realtsubd+space(254-len(target))
to
toseek=realtsubd+space(254-len(realtsubd))

I have left in, the diagnostic code in mk_sfs5.prg which shows on-screen what is going on with identification of the real target of the symlink.

Changes from Version 2 to Version 3:

1. Added copies of the gpl and lgpl.

2. Fixed a bug in mk_sfs5.prg. It was not detecting symlink targets correctly, when the target is a directory.

Changes from Version 1 to Version 2:

1. Fieldnames are now explicitly identified as to their containing database (and, an explanation of "m->"):

Although Clipper allows .dbf fields and memory variables to have identical names, I avoid the practice of giving memory variables the same names as .dbf fields, for the sake of clarity. However, in my code, among .dbf files there *are* duplicated field names, to make comparison-logic easier (i.e. "humanly possible") to write.

So, to make it easier for bash and C programmers to read my code, from version 2 on, I prefix all fieldnames with the name of the .dbf file to which the fieldname refers. For example,
"oldtree->identity99" means, "the field identity99 within the record you are now looking at, in file oldtree.dbf".

(***edited: in Clipper-speak, "the active file" means the .dbf file whose work area is now select'ed. "Work area" means, "pointer to a file handle" --Clipper allows 254 of them. It is not necessary for a .dbf to be currently the active one, in order to read from or write to it ("write to" means, to do a "replace __ with __" command on it), --to read from or write to a non-selected .dbf file, you simply prefix the fieldname with the .dbf's name-- but it *is* necessary that it be the active one in order to perform a seek, delete, or pack on it, or to print it out --for example, the way the bash script is written, by printing out the file script.dbf in mk_sfs5.prg***)

All variables which are not .dbf (database file) fields --in other words, all memory variables-- *can* be prefixed with "m->", which in Clipper distinguishes that the name refers to a memory variable and not to a field (within a record within a .dbf file). However, memory variables don't *need* to be prefixed with m-> ...with one exception: namely, when a memory variable is used as the parameter to the seek command.

I prefix memory variables with m-> only in seek commands, just because it is an idiosyncracy of seek that, in order to be reliable (i.e. not crash 30% of the time), seek needs m->.

Hence, beginning with version 2:

--all variables prefixed with something-> are fieldnames, except in seek commands where m-> denotes a memory variable.
--all variables without a something-> prefix are memory variables.

2. Minor edits to the comments for grammar, appearance, and clarity.

3. Minor changes to variable names to make them easier to read. Clipper only allows the names of both memory variables and fields, as well as function names, to be a maximum of 10 characters long. Names are not case-sensitive. (incidentally, Clipper Summer '87 also suffers from the eight-dot-three filename limitation of MS-DOS, though by using FAT32 shortnames files with long names can be manipulated, and it does not lose the longnames)
Last edited by Sit Heel Speak on Tue 16 Nov 2010, 17:49, edited 4 times in total.

User avatar
Sit Heel Speak
Posts: 2595
Joined: Fri 31 Mar 2006, 03:22
Location: downwind

posted --my Clipper packaging script for Puppy, version 4

#12 Post by Sit Heel Speak »

.
Attachments
makesfs_Clipper-v4.tar.gz
Clipper Summer '87 script (5 .prg's + .dbf's) to build a sourcebuilddir for Puppy --version 4
(50.53 KiB) Downloaded 901 times
Last edited by Sit Heel Speak on Sat 29 Oct 2011, 21:05, edited 3 times in total.

User avatar
technosaurus
Posts: 4853
Joined: Mon 19 May 2008, 01:24
Location: Blue Springs, MO
Contact:

#13 Post by technosaurus »

if you get one of those odd programs that disobeys DESTDIR you can edit the makefile to add it.

find "install:"
insert $(DESTDIR) before the destinations - usually right before $(prefix) OR $(libdir) OR $(bindir) ...you normally see them listed at the top of the ./configure --help message


but it's pretty easy to write a wrapper to capture the files in the final destination (I think it is always the last argument)

Code: Select all

#!/bin/sh
echo $# >> /tmp/files
/path/to/real/prog $@
Check out my [url=https://github.com/technosaurus]github repositories[/url]. I may eventually get around to updating my [url=http://bashismal.blogspot.com]blogspot[/url].

wetterau
Posts: 37
Joined: Wed 13 Jul 2005, 20:50

#14 Post by wetterau »

Clipper nostalgia. I made good money for years with Clipper. It was one of the first really practical data base oriented languages--fast, direct, and simple. All you needed was good d/b experience to go in and rescue the messes created by amateurs. Data is king, always was and still is. Just this morning, two guys at the next cafe table were trying to untangle a mess of spreadsheets that had their company sales info in various evolutionary stages. Same as it ever was. Somehow I managed to keep my mouth shut.
Thanks for the memory. I don't program now, write books. The ebook world is as fun now as the Clipper world was 25 years ago. Have been downloaded to 68 countries! (wetterau, feedbooks.com, all free). Enjoy, john

Post Reply