rsinfo 0.1.3

Add `vergen` to your [build-dependencies], then get all build info in single struct.
Documentation
#!/usr/bin/env python3

import sys
import os
import subprocess

ANCHOR_START = '// [ANCHOR START] CONST-TO-STRUCT.PY //'
ANCHOR_ENDED = '// [ANCHOR ENDED] CONST-TO-STRUCT.PY //'

def walk_mod(walk=os.walk, path='src'):
    for current, dirs, files in walk(path):
        for file in files:
            realpath = os.path.realpath(os.path.join(current, file))
            yield (realpath, single_mod(realpath))

# @param f = String of ".rs" file Path
# @return  = String of Rust Code
def single_mod(f):
    m = os.path.basename(f)
    is_mod = False
    if 'mod.rs' in m:
        is_mod = True
        m = os.path.basename(os.path.dirname(f))

    if m.endswith('.rs'):
        m = m[:-3]
    if '/cfg/' in f.replace('\\', '/'):
        if not is_mod:
            m = 'cfg_' + m

    code = ''
    with open(f, 'r') as fo:
        code = fo.read()
        code = code.replace('\n', ';').replace('\r', ';')
        code = code.split(';')
        code = [it for it in code if len(it) > 0]
        fo.close()

    line = ''

    def get_line():
        nonlocal line
        try:
            line = code.pop(0)
        except IndexError:
            return ''
        else:
            return line

    info_struct_name = '%s_info' % m
    info_struct = '\n'.join([
        '#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]',
        'pub struct %s {' % info_struct_name,
    ]) + '\n'

    all_info_fn = '\n'.join([
        'pub const fn all_info() -> %s {' % info_struct_name,
        '    %s {' % info_struct_name,
    ]) + '\n'

    all_info_json_impl = '\n'.join([
        '#[cfg(feature = "json")]',
        'impl %s {' % info_struct_name,
        '    pub fn to_json(&self) -> serde_json::Value {',
        '        serde_json::json!({',
    ]) + '\n'

    while len(get_line()) > 0:
        if 'pub const' in line and ':' in line and '=' in line:
            line = line.strip()

            skip = False
            for ex in ('pub fn all_info', 'pub const ALL_INFO', '/*', '*/', '//', '{', '}', ','):
                if ex in line:
                    skip = True
                    break

            if skip:
                continue

            for c in '\r\n\t':
                line = line.replace(c, '')

            # start parse

            line = line.replace('=', ':').replace(';', '')
            if line.startswith('pub const '):
                line = line[10:]
            
            words = line.split(':')
            const_name = words[0].strip()
            lower_name = const_name.lower()
            type = words[1].strip()

            info_struct += '    pub %s: %s,\n' % (lower_name, type)
            all_info_fn += '        %s: %s,\n' % (lower_name, const_name)

            cal = ''
            if type.endswith('_info'):
                cal = '.to_json()'
            all_info_json_impl += '            "%s": self.%s,\n' % (lower_name, lower_name+cal)

    info_struct += '}\n'
    all_info_fn += '    }\n}\n'
    all_info_json_impl += '        })\n    }\n}\n'

    all_info = 'pub const ALL_INFO: %s = all_info();\n' % info_struct_name
    
    out = info_struct + all_info + all_info_fn + all_info_json_impl
    prefix = '''%s

/*
 * the following source code is automatic generated by const-to-struct.py
 * DO NOT EDIT.
*/

''' % ANCHOR_START

    return prefix + out


def update_rs(f, code):
    orig = open(f, 'r').read()
    if ANCHOR_START not in orig:
        # Refusing to modify any unrelated source code files.
        raise SyntaxError('no "start anchor" in rust code.')

    start = orig.index(ANCHOR_START)
    ended = None
    try:
        ended = orig.index(ANCHOR_ENDED) + len(ANCHOR_ENDED)
    except ValueError:
        print('WARNING: no "end anchor" in rust code, assume to EOF (append to the end of ".rs" files)')

    new = orig[:start]
    if not ended:
        new += code
        new += ANCHOR_ENDED
    else:
        new += code
        new += ANCHOR_ENDED
        new += orig[ended:]

    if conf['dry_run']:
        print(code)
        return

    open(f, 'w').write(new)

def run():
    for file, code in walk_mod():
        print("Entering:", file)

        try:
            update_rs(file, code)
        except SyntaxError as e:
            print(repr(e))

conf = {
        'dry_run': False
        }
def main():
    try:
        if len(sys.argv[1])>0:
            conf['dry_run'] = True
            return run()
    except IndexError:
        pass

    p = subprocess.Popen(
            ['git', 'show', '-q'],
            stdin=subprocess.DEVNULL,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
    if p.wait() != 0:
        print('ERROR: Refusing to start, due to you are not in a git repo.')
        raise SystemExit(1)

    try:
        os.stat('Cargo.toml')
    except FileNotFoundError:
        print("ERROR: Refusing to start, due to Cargo.toml does not exists in current directory (PWD).")
        raise SystemExit(2)

    if len(os.popen("git diff --raw").read().replace('\r', '').replace('\n', '')) > 0:
        print('ERROR: Refusing to start, due to your git repo have some files that is not staged.')
        raise SystemExit(3)

    run()

if __name__ == '__main__':
    main()