Package: textform
textformA data transformation pipeline module based on the seminalPotter's Wheeldata wrangling formalism. The name is a portmanteau of "text" and "transform".Overviewtextform(abbreviatedtxf) is a text-oriented data transformation module. With it, you can create sequential record processingpipelinesthat convert data from (say) lines of text into records and then route the final record stream for another use (e.g, write the records to acsvfile.)Pipelines are cosntructed from a sequence oftransformsthat take in a record and modify it in some way. For example, theSplittransform will replace an input field with several new fields that are derived from the input by splitting on a pattern.While inspired by the Potter's Wheel transform list,textformis designed for practical everyday use. This means it includes transforms for limiting the number of rows, writing intermediate results to files and capturing via regular expressions.AudienceHow do I know iftextformis right for me? The simplest use case is where you want to use Python'sDictReaderbut the file isn't acsv. Withtextformyou can write a pipeline that will end up producing the records you would get fromDictReader.More complex use cases can be built on top of this kind of record stream. Reshaping, computing values, splitting, dividing, merging, filling in blanks and other kinds of data cleaning and preparation tasks can all be implemented in a reusable fashion withtextform. A pipeline effectively describes the format of a text file in an executable fashion that can be reused.ExampleI createdtextformbecause I had worked ona similar research systemin the past and had two text files produced by theDuckDBperformance test suite that I needed to convert intocsvs:------------------
|| Q01_PARALLEL ||
------------------
Cold Run...Done!
Run 1/5...0.12345
Run 1/5...0.12345
Run 1/5...0.12345
Run 1/5...0.12345
Run 1/5...0.12345
------------------
|| Q02_PARALLEL ||
------------------
...This file is esssentially a sequence of records grouped by higher attributes. Instead of writing a one-off Python script, I decided to write some simple transforms and build a pipeline, which looked like this:p=Text(sys.stdin,'Line')# Read a linep=Add(p,'Branch',sys.argv[1])# Tag the file with the branch namep=Match(p,'Line',r'------',invert=True).# Remove horizontal linesp=Divide(p,'Line','Query','Run',r'Q')# Separate the query names from the run datap=Fill(p,'Query','00')# Fill down the blank query namesp=Capture(p,'Query',('Query',),r'\|\|\s+Q(\w+)\s+\|\|')# Capture the query number# Split the execution mode from the query namep=Split(p,'Query',('Query','Mode',),r'_',('00','SERIAL',))p=Cast(p,'Query',int)# Cast the query number to an integerp=Match(p,'Run',r'\d')# Filter to the runs with data# Capture the run componentsp=Capture(p,'Run',('Run #','Run Count','Time',),r'(\d+)/(\d+)...(\d+\.\d+)')p=Cast(p,'Run #',int)# Cast the run componentsp=Cast(p,'Run Count',int)p=Cast(p,'Time',float)p=Write(p,sys.stdout)# Write the records to stdout as a csvp.pump()We can now invoke the pipeline script as:$python3pipeline.pymaster<performance.txt>performance.csvContributingYou know the drill: Fork, branch, test submit a PR.  This is a completely open source, free as in beer project.