<commit_before>import cffi
import os
import re

###
# TODO
#
# 1. Deal with leaks of Task and Rule objects, because of the handle cycle. We
#    need the C-API to allow us to do cleanup on the Task and Delegate user
#    contex pointers.

###

class AbstractError(Exception):
    pass

###

ffi = cffi.FFI()

# Load the defs by reading the llbuild header directly.
data = open(os.path.join(
    os.path.dirname(__file__),
    "../../products/libllbuild/public-api/llbuild/llbuild.h")).read()
# Strip out the directives.
data = re.sub("^#.*", "", data, 0, re.MULTILINE)
data = re.sub("LLBUILD_EXPORT", "", data, 0, re.MULTILINE)
ffi.cdef(data)

# Load the dylib.
#
# FIXME: Need a way to find this.
libllbuild = ffi.dlopen(os.path.join(
    os.path.dirname(__file__),
    "../../build/lib/libllbuild.dylib"))

@ffi.callback("void(void*, void*, llb_task_t*)")
def _task_start(context, engine_context, _task):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    task.start(engine)

@ffi.callback(
    "void(void*, void*, llb_task_t*, uintptr_t, llb_data_t*)")
def _task_provide_value(context, engine_context, task, input_id, value):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    task.provide_value(engine, input_id,
                       str(ffi.buffer(value.data, value.length)))
    
@ffi.callback("void(void*, void*, llb_task_t*)")
def _task_inputs_available(context, engine_context, llb_task):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    # Notify the task.
    task.inputs_available(engine)

@ffi.callback("llb_task_t*(void*, void*)")
def _rule_create_task(context, engine_context):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    # Create the task.
    task = rule.create_task()
    assert isinstance(task, Task)

    # FIXME: Should we pass the context pointer separately from the delegate
    # structure, so it can be reused?
    delegate = ffi.new("llb_task_delegate_t*")
    delegate.context = task._handle
    delegate.start = _task_start
    delegate.provide_value = _task_provide_value
    delegate.inputs_available = _task_inputs_available

    task._task = libllbuild.llb_task_create(delegate[0])
    return libllbuild.llb_buildengine_register_task(engine._engine, task._task)

@ffi.callback("bool(void*, void*, llb_rule_t*, llb_data_t*)")
def _rule_is_result_valid(context, engine_context, rule, value):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    return rule.is_result_valid(
        engine, str(ffi.buffer(value.data, value.length)))

@ffi.callback("void(void*, void*, llb_rule_status_kind_t)")
def _rule_update_status(context, engine_context, kind):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    rule.update_status(engine, kind)

@ffi.callback("void(void*, llb_data_t*, llb_rule_t*)")
def _buildengine_lookup_rule(context, key, rule_out):
    engine = ffi.from_handle(context)
    rule = engine.delegate.lookup_rule(str(ffi.buffer(key.data, key.length)))

    # Initialize the rule result from the given object.
    assert isinstance(rule, Rule)

    # FIXME: What is to prevent the rule from being deallocated after this
    # point? We only are passing back a handle.
    rule_out.context = rule._handle
    rule_out.create_task = _rule_create_task
    rule_out.is_result_valid = _rule_is_result_valid
    rule_out.update_status = _rule_update_status
    
class _Data(object):
    """Wrapper for a key and its data."""
    def __init__(self, name):
        self.key = ffi.new("llb_data_t*")
        self.key.length = length = len(name)
        # Store in a local to keep alive.
        self._datap = ffi.new("char[]", length)
        self.key.data = self._datap
        # Copy in the data.
        ffi.buffer(self.key.data, length)[:] = name

class _HandledObject(object):
    _handle_cache = None
    @property
    def _handle(self):
        # FIXME: This creates a cycle.
        if self._handle_cache is None:
            self._handle_cache = ffi.new_handle(self)
        return self._handle_cache
            
###

def get_full_version():
    return ffi.string(libllbuild.llb_get_full_version_string())
        
class Task(_HandledObject):
    _task = None<commit_msg>[bindings] Fix some issues with the bindings.<commit_after>import cffi
import os
import re

###
# TODO
#
# 1. Deal with leaks of Task and Rule objects, because of the handle cycle. We
#    need the C-API to allow us to do cleanup on the Task and Delegate user
#    contex pointers.

###

class AbstractError(Exception):
    pass

###

ffi = cffi.FFI()

# Load the defs by reading the llbuild header directly.
data = open(os.path.join(
    os.path.dirname(__file__),
    "../../products/libllbuild/public-api/llbuild/llbuild.h")).read()
# Strip out the directives.
data = re.sub("^#.*", "", data, 0, re.MULTILINE)
data = re.sub("LLBUILD_EXPORT", "", data, 0, re.MULTILINE)
ffi.cdef(data)

# Load the dylib.
#
# FIXME: Need a way to find this.
libllbuild = ffi.dlopen(os.path.join(
    os.path.dirname(__file__),
    "../../build/lib/libllbuild.dylib"))

@ffi.callback("void(void*, void*, llb_task_t*)")
def _task_start(context, engine_context, _task):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    task.start(engine)

@ffi.callback(
    "void(void*, void*, llb_task_t*, uintptr_t, llb_data_t*)")
def _task_provide_value(context, engine_context, task, input_id, value):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    task.provide_value(engine, input_id,
                       str(ffi.buffer(value.data, value.length)))
    
@ffi.callback("void(void*, void*, llb_task_t*)")
def _task_inputs_available(context, engine_context, llb_task):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    # Notify the task.
    task.inputs_available(engine)

@ffi.callback("llb_task_t*(void*, void*)")
def _rule_create_task(context, engine_context):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    # Create the task.
    task = rule.create_task()
    assert isinstance(task, Task)

    # FIXME: Should we pass the context pointer separately from the delegate
    # structure, so it can be reused?
    delegate = ffi.new("llb_task_delegate_t*")
    delegate.context = task._handle
    delegate.start = _task_start
    delegate.provide_value = _task_provide_value
    delegate.inputs_available = _task_inputs_available

    task._task = libllbuild.llb_task_create(delegate[0])
    return libllbuild.llb_buildengine_register_task(engine._engine, task._task)

@ffi.callback("bool(void*, void*, llb_rule_t*, llb_data_t*)")
def _rule_is_result_valid(context, engine_context, rule, value):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    return rule.is_result_valid(
        engine, str(ffi.buffer(value.data, value.length)))

@ffi.callback("void(void*, void*, llb_rule_status_kind_t)")
def _rule_update_status(context, engine_context, kind):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    rule.update_status(engine, kind)

@ffi.callback("void(void*, llb_data_t*, llb_rule_t*)")
def _buildengine_lookup_rule(context, key, rule_out):
    engine = ffi.from_handle(context)
    rule = engine.delegate.lookup_rule(str(ffi.buffer(key.data, key.length)))

    # Initialize the rule result from the given object.
    assert isinstance(rule, Rule)

    # FIXME: What is to prevent the rule from being deallocated after this
    # point? We only are passing back a handle.
    rule_out.context = rule._handle
    rule_out.create_task = _rule_create_task
    rule_out.is_result_valid = _rule_is_result_valid
    rule_out.update_status = _rule_update_status
    
class _Data(object):
    """Wrapper for a key and its data."""
    def __init__(self, name):
        name = str(name)
        self.key = ffi.new("llb_data_t*")
        self.key.length = length = len(name)
        # Store in a local to keep alive.
        self._datap = ffi.new("char[]", length)
        self.key.data = self._datap
        # Copy in the data.
        ffi.buffer(self.key.data, length)[:] = name

class _HandledObject(object):
    _all_handles = []
    _handle_cache = None
    @property
    def _handle(self):
        # FIXME: This creates a cycle.
        if self._handle_cache is None:
            self._handle_cache = handle = ffi.new_handle(self)
            # FIXME: This leaks everything, but we are currently dropping a
            # handle somewhere.
            self._all_handles.append(handle)
        return self._handle_cache
            
###

def get_full_version():
    return ffi.string(libllbuild.llb_get_full_version_string())
        
class Task(_HandledObject):
    _task = None
{'fraction_non_alphanumeric': 0.11703204047217539, 'fraction_numerical': 0.0008993816750983699, 'mean_word_length': 3.6527196652719667, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '209745', 'n_tokens_mistral': 3015, 'n_tokens_neox': 2968, 'n_words': 678}