Welcome to runif’s documentation!

Welcome to Run…if

Runif is an idempotent and minimal python 3 library for rapid scripting. Provide support for creating file, adding data to them, patching and so on.

run if in a slogan: - Execute idempotent scripts based on file contents. - Battle tested on over 60 conversions - No dependency: it works with python library out of the box

Simple usage: first example

from runif import *

def demo_python_builder(fname):
    with open(fname,"w") as f:
        f.writelines([
            "def testme(str):\n",
            "\tprintln('Message from "+fname+" '+str)\n",
            "\n",
            "testme('whooa')\n"
        ])

run_if_missed("workdir", lambda dir_to_build:  (Path(dir_to_build)).mkdir() )
for x in range(4):
    run_if_missed("workdir/demo"+str(x)+".py", demo_python_builder)

Create workdir if missed.

Complex example

zconnect2Gradle.py is a complex example we used on a production site.

We need a way to convert several (hundreds) of IBM ZConnect project(s) to Gradle. Source project was done on Eclipse via a propietary plugin. The following script will read the provided directory, added a small gradle property file and collect all the data in a master yaml file called “recap”.

#!/usr/bin/env python
# Python3 dep wizard

import hashlib
import sys
import functools, re
import zipfile, os

import click

from runif import *


# Example of a fragment added to the recap
# See https://pyformat.info/
# '{first} {last}'.format(**data)
DEP_DESC="""    {appName}:
      application_name: {appName}
      component_type: 'zconnect_{appType}'
      group_id: 'my.shiny.project'
      artifact_id: '{artifactId}'
      version: '1.0.0-SNAPSHOT'
      """

# aar for api and sar for sevices
PROP_TEMPLATE="""
name=%s
group=my.shiny.project
version=1.0.0-SNAPSHOT
archiveType=%s
"""
def create_gradle_prop(projectName, propfile):
    print("Creating",propfile, "for", projectName)
    # Compute application name
    appName=projectName
    if "-API" in projectName:
        appType="aar"
    else:
        appType="sar"
    actualized_props=PROP_TEMPLATE % (appName, appType)
    with open(propfile,"w") as f:
        f.write(actualized_props)
    data={ "appName": appName.replace('-','_'), "appType": appType, "artifactId":appName}
    dep_desc_fragment=DEP_DESC.format(**data )
    append_if_missed("recap.yml", dep_desc_fragment)

def create_master_recap(fname):
    with open(fname,"w") as f:
        f.write("""
  # Describe ZConnect deploy
recap_data:  
  project_list:
""")


def process(project):
    run_if_missed("recap.yml", create_master_recap)
    run_if_missed(project+"/gradle.properties", lambda f: create_gradle_prop(project, f))

@click.command()
@click.argument("projectdirs", nargs=-1)
def zconnect2gradle(projectdirs):
    """
    Given a set of Zconnect directories
    Create a gradle.properties for each project and try also to build a deployment descriptor.

    Version are fixed to 1.0.0-SNAPSHOT and group is fixed to my.shiny.project
    
    It is able to detect sar or aar via naming convention.
    Bring the entire project name as artifact name    
    """
    for project in projectdirs:
        process(project)


if __name__=="__main__":
    zconnect2gradle()

Runif functions

exception runif.RunifError

Raised by run function if the called command has a return code != 0.

runif.append_if_missed(fname, *args)

You pass a list of strings. For every string, if it is not already present on fname, it is appended.

runif.extract_var(fname, var_name)

Extract a var from a simple property file (with assignments). Property files are like Java property files with syntax:

prop=value

It can be useful to extract data from gradle.properties, spring properties or so on

runif.regexp_replace_file(filename: str, compiled_regexp, replace_str: str)

Smart way of replacing a compiled regexp in a file Rewrite the file only if really needed

runif.run(cmd, extra_param=None)

Supports one extra quoted params in append

runif.run_each(path: str, glob: str, func)

Scan files and run in a sequential fashion. The func must return True if make some changes

runif.run_each_async(path: str, glob: str, func, pool_size: int = 1)

Scan files and run in a multi-process fashion. The func must return True if make some changes

The function must AVOID calling not hread-safe function like run_if_modified

Spawn overhead is low but parallelism is not high because of ThreadPoolExecutor

runif.run_if_missed(fname: str, funx)

If fname is missed, execute funx(fname) Return True if call the function, False otherwise.

runif.run_if_modified(fname: str, funx, cache_file='.runif_cache')

Run the function if fname is modified with respect of last run. To check for modification a md5 signature is used. The signature are stored in a local cache_file the caller can customize. It works well with small files, it can be slow on larger one.

runif.run_if_present(fname: str, funx)

If fname is present, execute funx(fname) Return True if call the function, False otherwise.

runif.run_if_unmarked(fname, marker, fun_to_call_if_unmarked)

Run the function if the string marker is not found inside file fname Marker can be substring of a line. Tipical usage secnario is to run a function which modify the content of a file.

The function will be called with two parameters: filename and marker so it can handle more than one function type.

Indices and tables