text:
package io.druid.segment.incremental;

import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.metamx.common.IAE;
import com.metamx.common.ISE;
import io.druid.data.input.InputRow;
import io.druid.data.input.MapBasedRow;
import io.druid.data.input.Row;
import io.druid.data.input.impl.DimensionsSpec;
import io.druid.data.input.impl.SpatialDimensionSchema;
import io.druid.granularity.QueryGranularity;
import io.druid.query.aggregation.AggregatorFactory;
import io.druid.query.aggregation.PostAggregator;
import io.druid.query.dimension.DimensionSpec;
import io.druid.query.extraction.ExtractionFn;
import io.druid.segment.ColumnSelectorFactory;
import io.druid.segment.DimensionSelector;
import io.druid.segment.FloatColumnSelector;
import io.druid.segment.LongColumnSelector;
import io.druid.segment.Metadata;
import io.druid.segment.ObjectColumnSelector;
import io.druid.segment.column.Column;
import io.druid.segment.column.ColumnCapabilities;
import io.druid.segment.column.ColumnCapabilitiesImpl;
import io.druid.segment.column.ValueType;
import io.druid.segment.data.IndexedInts;
import io.druid.segment.serde.ComplexMetricExtractor;
import io.druid.segment.serde.ComplexMetricSerde;
import io.druid.segment.serde.ComplexMetrics;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
 */
public abstract class IncrementalIndex<AggregatorType> implements Iterable<Row>, Closeable
{
  private volatile DateTime maxIngestedEventTime;

  public static ColumnSelectorFactory makeColumnSelectorFactory(
      final AggregatorFactory agg,
      final Supplier<InputRow> in,
      final boolean deserializeComplexMetrics
  )
  {
    return new ColumnSelectorFactory()
    {
      @Override
      public LongColumnSelector makeLongColumnSelector(final String columnName)
      {
        if (columnName.equals(Column.TIME_COLUMN_NAME)) {
          return new LongColumnSelector()
          {
            @Override
            public long get()
            {
              return in.get().getTimestampFromEpoch();
            }
          };
        }
        return new LongColumnSelector()
        {
          @Override
          public long get()
          {
            return in.get().getLongMetric(columnName);
          }
        };
      }

      @Override
      public FloatColumnSelector makeFloatColumnSelector(final String columnName)
      {
        return new FloatColumnSelector()
        {
          @Override
          public float get()
          {
            return in.get().getFloatMetric(columnName);
          }
        };
      }

      @Override
      public ObjectColumnSelector makeObjectColumnSelector(final String column)
      {
        final String typeName = agg.getTypeName();

        final ObjectColumnSelector<Object> rawColumnSelector = new ObjectColumnSelector<Object>()
        {
          @Override
          public Class classOfObject()
          {
            return Object.class;
          }

          @Override
          public Object get()
          {
            return in.get().getRaw(column);
          }
        };

        if (!deserializeComplexMetrics) {
          return rawColumnSelector;
        } else {
          if (typeName.equals("float")) {
            return rawColumnSelector;
          }

          final ComplexMetricSerde serde = ComplexMetrics.getSerdeForType(typeName);
          if (serde == null) {
            throw new ISE("Don't know how to handle type[%s]", typeName);
          }

          final ComplexMetricExtractor extractor = serde.getExtractor();
          return new ObjectColumnSelector()
          {
            @Override
            public Class classOfObject()
            {
              return extractor.extractedClass();
            }

            @Override
            public Object get()
            {
              return extractor.extractValue(in.get(), column);
            }
          };
        }
      }

      @Override
      public DimensionSelector makeDimensionSelector(
          DimensionSpec dimensionSpec
      )
      {
        return dimensionSpec.decorate(makeDimensionSelectorUndecorated(dimensionSpec));
      }

      private DimensionSelector makeDimensionSelectorUndecorated(
          DimensionSpec dimensionSpec
      )
      {
        final String dimension = dimensionSpec.getDimension();
        final ExtractionFn extractionFn = dimensionSpec.getExtractionFn();

        return new DimensionSelector()
        {
          @Override
          public IndexedInts getRow()
          {
            final List<String> dimensionValues = in.get().getDimension(dimension);
            final ArrayList<Integer> vals = Lists.newArrayList();
            if (dimensionValues != null) {
              for (int i = 0; i < dimensionValues.size(); ++i) {
                vals.add(i);
              }
            }

            return new IndexedInts()
            {
              @Override
              public int size()
              {
                return vals.size();
              }

              @Override
              public int get(int index)
              {
                return vals.get(index);
              }

              @Override
              public Iterator<Integer> iterator()
              {
                return vals.iterator();
              }

              @Override
              public void close() throws IOException
              {

              }

              @Override
              public void fill(int index, int[] toFill)
              {
                throw new UnsupportedOperationException("fill not supported");
              }
            };
          }

          @Override
          public int getValueCardinality()
          {
            throw new UnsupportedOperationException("value cardinality is unknown in incremental index");
          }

          @Override
          public String lookupName(int id)
          {
            final String value = in.get().getDimension(dimension).get(id);
            return extractionFn == null ? value : extractionFn.apply(value);
          }

          @Override
          public int lookupId(String name)
          {
            if (extractionFn != null) {
              throw new UnsupportedOperationException("cannot perform lookup when applying an extraction function");
            }
            return in.get().getDimension(dimension).indexOf(name);
          }
        };
      }
    };
  }

  private final long minTimestamp;
  private final QueryGranularity gran;
  private final List<Function<InputRow, InputRow>> rowTransformers;
  private final AggregatorFactory[] metrics;
  private final AggregatorType[] aggs;
  private final boolean deserializeComplexMetrics;
  private final Metadata metadata;

  private final Map<String, MetricDesc> metricDescs;
  private final Map<String, DimensionDesc> dimensionDescs;
  private final Map<String, ColumnCapabilitiesImpl> columnCapabilities;

  private final AtomicInteger numEntries = new AtomicInteger();

  // This is modified on add() in a critical section.
  private final ThreadLocal<InputRow> in = new ThreadLocal<>();
  private final Supplier<InputRow> rowSupplier = new Supplier<InputRow>()
  {
    @Override
    public InputRow get()
    {
      return in.get();
    }
  };

  /**
   * Setting deserializeComplexMetrics to false is necessary for intermediate aggregation such as groupBy that
   * should not deserialize input columns using ComplexMetricSerde for aggregators that return complex metrics.
   *
   * @param incrementalIndexSchema    the schema to use for incremental index
   * @param deserializeComplexMetrics flag whether or not to call ComplexMetricExtractor.extractValue() on the input
   *                                  value for aggregators that return metrics other than float.
   */
  public IncrementalIndex(
      final IncrementalIndexSchema incrementalIndexSchema,
      final boolean deserializeComplexMetrics
  )
  {
    this.minTimestamp = incrementalIndexSchema.getMinTimestamp();
    this.gran = incrementalIndexSchema.getGran();
    this.metrics = incrementalIndexSchema.getMetrics();
    this.rowTransformers = new CopyOnWriteArrayList<>();
    this.deserializeComplexMetrics = deserializeComplexMetrics;

    this.metadata = new Metadata().setAggregators(getCombiningAggregators(metrics));

    this.aggs = initAggs(metrics, rowSupplier, deserializeComplexMetrics);
    this.columnCapabilities = Maps.newHashMap();

    this.metricDescs = Maps.newLinkedHashMap();
    for (AggregatorFactory metric : metrics) {
      MetricDesc metricDesc = new MetricDesc(metricDescs.size(), metric);
      metricDescs.put(metricDesc.getName(), metricDesc);
      columnCapabilities.put(metricDesc.getName(), metricDesc.getCapabilities());
    }

    DimensionsSpec dimensionsSpec = incrementalIndexSchema.getDimensionsSpec();

    this.dimensionDescs = Maps.newLinkedHashMap();
    for (String dimension : dimensionsSpec.getDimensions()) {
      ColumnCapabilitiesImpl capabilities = new ColumnCapabilitiesImpl();
      capabilities.setType(ValueType.STRING);
      dimensionDescs.put(
          dimension,
          new DimensionDesc(dimensionDescs.size(), dimension, newDimDim(dimension), capabilities)
      );
      columnCapabilities.put(dimension, capabilities);
    }

    // This should really be more generic
    List<SpatialDimensionSchema> spatialDimensions = dimensionsSpec.getSpatialDimensions();
    if (!spatialDimensions.isEmpty()) {
      this.rowTransformers.add(new SpatialDimensionRowTransformer(spatialDimensions));
    }
    for (SpatialDimensionSchema spatialDimension : spatialDimensions) {
      ColumnCapabilitiesImpl capabilities = new ColumnCapabilitiesImpl();
      capabilities.setType(ValueType.STRING);
      capabilities.setHasSpatialIndexes(true);
      columnCapabilities.put(spatialDimension.getDimName(), capabilities);
    }
  }

  private DimDim newDimDim(String dimension)
  {
    return new NullValueConverterDimDim(makeDimDim(dimension));
  }

  public abstract ConcurrentNavigableMap<TimeAndDims, Integer> getFacts();

  public abstract boolean canAppendRow();

  public abstract String getOutOfRowsReason();

  // use newDimDim
  protected abstract DimDim makeDimDim(String dimension);

  protected abstract AggregatorType[] initAggs(
      AggregatorFactory[] metrics,
      Supplier<InputRow> rowSupplier,
      boolean deserializeComplexMetrics
  );

  // Note: This method needs to be thread safe.
  protected abstract Integer addToFacts(
      AggregatorFactory[] metrics,
      boolean deserializeComplexMetrics,
      InputRow row,
      AtomicInteger numEntries,
      TimeAndDims key,
      ThreadLocal<InputRow> rowContainer,
      Supplier<InputRow> rowSupplier
  ) throws IndexSizeExceededException;

  protected abstract AggregatorType[] getAggsForRow(int rowOffset);

  protected abstract Object getAggVal(AggregatorType agg, int rowOffset, int aggPosition);

  protected abstract float getMetricFloatValue(int rowOffset, int aggOffset);

  protected abstract long getMetricLongValue(int rowOffset, int aggOffset);

  protected abstract Object getMetricObjectValue(int rowOffset, int aggOffset);

  @Override
  public void close()
  {
    // Nothing to close
  }

  public InputRow formatRow(InputRow row)
  {
    for (Function<InputRow, InputRow> rowTransformer : rowTransformers) {
      row = rowTransformer.apply(row);
    }

    if (row == null) {
      throw new IAE("Row is null? How can this be?!");
    }
    return row;
  }

  /**
   * Adds a new row.  The row might correspond with another row that already exists, in which case this will
   * update that row instead of inserting a new one.
   *

   *

   * Calls to add() are thread safe.
   *

   *
   * @param row the row of data to add
   *
   * @return the number of rows in the data set after adding the InputRow
   */
  public int add(InputRow row) throws IndexSizeExceededException
  {
    row = formatRow(row);
    if (row.getTimestampFromEpoch() < minTimestamp) {
      throw new IAE("Cannot add row[%s] because it is below the minTimestamp[%s]", row, new DateTime(minTimestamp));
    }

    final List<String> rowDimensions = row.getDimensions();

    String[][] dims;
    List<String[]> overflow = null;
    synchronized (dimensionDescs) {
      dims = new String[dimensionDescs.size()][];
      for (String dimension : rowDimensions) {
        List<String> dimensionValues = row.getDimension(dimension);

        ColumnCapabilitiesImpl capabilities;
        DimensionDesc desc = dimensionDescs.get(dimension);
        if (desc != null) {
          capabilities = desc.getCapabilities();
        } else {
          capabilities = columnCapabilities.get(dimension);
          if (capabilities == null) {
            capabilities = new ColumnCapabilitiesImpl();
            capabilities.setType(ValueType.STRING);
            columnCapabilities.put(dimension, capabilities);
          }
        }

        // Set column capabilities as data is coming in
        if (!capabilities.hasMultipleValues() && dimensionValues.size() > 1) {
          capabilities.setHasMultipleValues(true);
        }

        if (desc == null) {
          desc = new DimensionDesc(dimensionDescs.size(), dimension, newDimDim(dimension), capabilities);
          dimensionDescs.put(dimension, desc);

          if (overflow == null) {
            overflow = Lists.newArrayList();
          }
          overflow.add(getDimVals(desc.getValues(), dimensionValues));
        } else if (desc.getIndex() > dims.length || dims[desc.getIndex()] != null) {
          /*
           * index > dims.length requires that we saw this dimension and added it to the dimensionOrder map,
           * otherwise index is null. Since dims is initialized based on the size of dimensionOrder on each call to add,
           * it must have been added to dimensionOrder during this InputRow.
           *
           * if we found an index for this dimension it means we've seen it already. If !(index > dims.length) then
           * we saw it on a previous input row (this its safe to index into dims). If we found a value in
           * the dims array for this index, it means we have seen this dimension already on this input row.
           */
          throw new ISE("Dimension[%s] occurred more than once in InputRow", dimension);
        } else {
          dims[desc.getIndex()] = getDimVals(desc.getValues(), dimensionValues);
        }
      }
    }

    if (overflow != null) {
      // Merge overflow and non-overflow
      String[][] newDims = new String[dims.length + overflow.size()][];
      System.arraycopy(dims, 0, newDims, 0, dims.length);
      for (int i = 0; i < overflow.size(); ++i) {
        newDims[dims.length + i] = overflow.get(i);
      }
      dims = newDims;
    }

    final TimeAndDims key = new TimeAndDims(Math.max(gran.truncate(row.getTimestampFromEpoch()), minTimestamp), dims);
    final Integer rv = addToFacts(metrics, deserializeComplexMetrics, row, numEntries, key, in, rowSupplier);
    updateMaxIngestedTime(row.getTimestamp());
    return rv;
  }

  public synchronized void updateMaxIngestedTime(DateTime eventTime)
  {
    if (maxIngestedEventTime == null || maxIngestedEventTime.isBefore(eventTime)) {
      maxIngestedEventTime = eventTime;
    }
  }

  public boolean isEmpty()
  {
    return numEntries.get() == 0;
  }

  public int size()
  {
    return numEntries.get();
  }

  private long getMinTimeMillis()
  {
    return getFacts().firstKey().getTimestamp();
  }

  private long getMaxTimeMillis()
  {
    return getFacts().lastKey().getTimestamp();
  }

  private String[] getDimVals(final DimDim dimLookup, final List<String> dimValues)
  {
    final String[] retVal = new String[dimValues.size()];
    if (dimValues.size() == 0) {
      // NULL VALUE
      if (!dimLookup.contains(null)) {
        dimLookup.add(null);
      }
      return null;
    }
    int count = 0;
    for (String dimValue : dimValues) {
      String canonicalDimValue = dimLookup.get(dimValue);
      if (!dimLookup.contains(canonicalDimValue)) {
        dimLookup.add(dimValue);
      }
      retVal[count] = canonicalDimValue;
      count++;
    }
    Arrays.sort(retVal);

    return retVal;
  }

  public AggregatorType[] getAggs()
  {
    return aggs;
  }

  public AggregatorFactory[] getMetricAggs()
  {
    return metrics;
  }

  public List<String> getDimensionNames()
  {
    synchronized (dimensionDescs) {
      return ImmutableList.copyOf(dimensionDescs.keySet());
    }
  }

  public List<DimensionDesc> getDimensions()
  {
    synchronized (dimensionDescs) {
      return ImmutableList.copyOf(dimensionDescs.values());
    }
  }

  public DimensionDesc getDimension(String dimension)
  {
    synchronized (dimensionDescs) {
      return dimensionDescs.get(dimension);
    }
  }

  public String getMetricType(String metric)
  {
    final MetricDesc metricDesc = metricDescs.get(metric);
    return metricDesc != null ? metricDesc.getType() : null;
  }

  public Interval getInterval()
  {
    return new Interval(minTimestamp, isEmpty() ? minTimestamp : gran.next(getMaxTimeMillis()));
  }

  public DateTime getMinTime()
  {
    return isEmpty() ? null : new DateTime(getMinTimeMillis());
  }

  public DateTime getMaxTime()
  {
    return isEmpty() ? null : new DateTime(getMaxTimeMillis());
  }

  public DimDim getDimensionValues(String dimension)
  {
    DimensionDesc dimSpec = getDimension(dimension);
    return dimSpec == null ? null : dimSpec.getValues();
  }

  public Integer getDimensionIndex(String dimension)
  {
    DimensionDesc dimSpec = getDimension(dimension);
    return dimSpec == null ? null : dimSpec.getIndex();
  }

  public List<String> getDimensionOrder()
  {
    synchronized (dimensionDescs) {
      return ImmutableList.copyOf(dimensionDescs.keySet());
    }
  }

  /*
   * Currently called to initialize IncrementalIndex dimension order during index creation
   * Index dimension ordering could be changed to initalize from DimensionsSpec after resolution of
   * https://github.com/druid-io/druid/issues/2011
   */
  public void loadDimensionIterable(Iterable<String> oldDimensionOrder)
  {
    synchronized (dimensionDescs) {
      if (!dimensionDescs.isEmpty()) {
        throw new ISE("Cannot load dimension order when existing order[%s] is not empty.", dimensionDescs.keySet());
      }
      for (String dim : oldDimensionOrder) {
        if (dimensionDescs.get(dim) == null) {
          ColumnCapabilitiesImpl capabilities = new ColumnCapabilitiesImpl();
          capabilities.setType(ValueType.STRING);
          columnCapabilities.put(dim, capabilities);
          DimensionDesc desc = new DimensionDesc(dimensionDescs.size(), dim, newDimDim(dim), capabilities);
          dimensionDescs.put(dim, desc);
        }
      }
    }
  }

  public List<String> getMetricNames()
  {
    return ImmutableList.copyOf(metricDescs.keySet());
  }

  public List<MetricDesc> getMetrics()
  {
    return ImmutableList.copyOf(metricDescs.values());
  }

  public Integer getMetricIndex(String metricName)
  {
    MetricDesc metSpec = metricDescs.get(metricName);
    return metSpec == null ? null : metSpec.getIndex();
  }

  public ColumnCapabilities getCapabilities(String column)
  {
    return columnCapabilities.get(column);
  }

  public ConcurrentNavigableMap<TimeAndDims, Integer> getSubMap(TimeAndDims start, TimeAndDims end)
  {
    return getFacts().subMap(start, end);
  }

  public Metadata getMetadata()
  {
    return metadata;
  }

  private static AggregatorFactory[] getCombiningAggregators(AggregatorFactory[] aggregators)
  {
    AggregatorFactory[] combiningAggregators = new AggregatorFactory[aggregators.length];
    for (int i = 0; i < aggregators.length; i++) {
      combiningAggregators[i] = aggregators[i].getCombiningFactory();
    }
    return combiningAggregators;
  }

  @Override
  public Iterator<Row> iterator()
  {
    return iterableWithPostAggregations(null, false).iterator();
  }

  public Iterable<Row> iterableWithPostAggregations(final List<PostAggregator> postAggs, final boolean descending)
  {
    final List<String> dimensions = getDimensionNames();
    final ConcurrentNavigableMap<TimeAndDims, Integer> facts = descending ? getFacts().descendingMap() : getFacts();
    return new Iterable<Row>()
    {
      @Override
      public Iterator<Row> iterator()
      {
        return Iterators.transform(
            facts.entrySet().iterator(),
            new Function<Map.Entry<TimeAndDims, Integer>, Row>()
            {
              @Override
              public Row apply(final Map.Entry<TimeAndDims, Integer> input)
              {
                final TimeAndDims timeAndDims = input.getKey();
                final int rowOffset = input.getValue();

                String[][] theDims = timeAndDims.getDims();

                Map<String, Object> theVals = Maps.newLinkedHashMap();
                for (int i = 0; i < theDims.length; ++i) {
                  String[] dim = theDims[i];
                  if (dim != null && dim.length != 0) {
                    theVals.put(dimensions.get(i), dim.length == 1 ? dim[0] : Arrays.asList(dim));
                  } else {
                    theVals.put(dimensions.get(i), null);
                  }
                }

                AggregatorType[] aggs = getAggsForRow(rowOffset);
                for (int i = 0; i < aggs.length; ++i) {
                  theVals.put(metrics[i].getName(), getAggVal(aggs[i], rowOffset, i));
                }

                if (postAggs != null) {
                  for (PostAggregator postAgg : postAggs) {
                    theVals.put(postAgg.getName(), postAgg.compute(theVals));
                  }
                }

                return new MapBasedRow(timeAndDims.getTimestamp(), theVals);
              }
            }
        );
      }
    };
  }

  public DateTime getMaxIngestedEventTime()
  {
    return maxIngestedEventTime;
  }

  public static class DimensionDesc
  {
    private final int index;
    private final String name;
    private final DimDim values;
    private final ColumnCapabilitiesImpl capabilities;

    public DimensionDesc(int index, String name, DimDim values, ColumnCapabilitiesImpl capabilities)
    {
      this.index = index;
      this.name = name;
      this.values = values;
      this.capabilities = capabilities;
    }

    public int getIndex()
    {
      return index;
    }

    public String getName()
    {
      return name;
    }

    public DimDim getValues()
    {
      return values;
    }

    public ColumnCapabilitiesImpl getCapabilities()
    {
      return capabilities;
    }
  }

  public static class MetricDesc
  {
    private final int index;
    private final String name;
    private final String type;
    private final ColumnCapabilitiesImpl capabilities;

    public MetricDesc(int index, AggregatorFactory factory)
    {
      this.index = index;
      this.name = factory.getName();
      this.type = factory.getTypeName();
      this.capabilities = new ColumnCapabilitiesImpl();
      if (type.equalsIgnoreCase("float")) {
        capabilities.setType(ValueType.FLOAT);
      } else if (type.equalsIgnoreCase("long")) {
        capabilities.setType(ValueType.LONG);
      } else {
        capabilities.setType(ValueType.COMPLEX);
      }
    }

    public int getIndex()
    {
      return index;
    }

    public String getName()
    {
      return name;
    }

    public String getType()
    {
      return type;
    }

    public ColumnCapabilitiesImpl getCapabilities()
    {
      return capabilities;
    }
  }

  static interface DimDim
  {
    public String get(String value);

    public int getId(String value);

    public String getValue(int id);

    public boolean contains(String value);

    public int size();

    public int add(String value);

    public int getSortedId(String value);

    public String getSortedValue(int index);

    public void sort();

    public boolean compareCanonicalValues(String s1, String s2);
  }

  /**
   * implementation which converts null strings to empty strings and vice versa.
   */
  static class NullValueConverterDimDim implements DimDim
  {
    private final DimDim delegate;

    NullValueConverterDimDim(DimDim delegate)
    {
      this.delegate = delegate;
    }

    @Override
    public String get(String value)
    {
      return delegate.get(Strings.nullToEmpty(value));
    }

    @Override
    public int getId(String value)
    {
      return delegate.getId(Strings.nullToEmpty(value));
    }

    @Override
    public String getValue(int id)
    {
      return Strings.emptyToNull(delegate.getValue(id));
    }

    @Override
    public boolean contains(String value)
    {
      return delegate.contains(Strings.nullToEmpty(value));
    }

    @Override
    public int size()
    {
      return delegate.size();
    }

    @Override
    public int add(String value)
    {
      return delegate.add(Strings.nullToEmpty(value));
    }

    @Override
    public int getSortedId(String value)
    {
      return delegate.getSortedId(Strings.nullToEmpty(value));
    }

    @Override
    public String getSortedValue(int index)
    {
      return Strings.emptyToNull(delegate.getSortedValue(index));
    }

    @Override
    public void sort()
    {
      delegate.sort();
    }

    @Override
    public boolean compareCanonicalValues(String s1, String s2)
    {
      return delegate.compareCanonicalValues(Strings.nullToEmpty(s1), Strings.nullToEmpty(s2));
    }
  }

  static class TimeAndDims implements Comparable<TimeAndDims>
  {
    private final long timestamp;
    private final String[][] dims;

    TimeAndDims(
        long timestamp,
        String[][] dims
    )
    {
      this.timestamp = timestamp;
      this.dims = dims;
    }

    long getTimestamp()
    {
      return timestamp;
    }

    String[][] getDims()
    {
      return dims;
    }

    @Override
    public int compareTo(TimeAndDims rhs)
    {
      int retVal = Longs.compare(timestamp, rhs.timestamp);
      int numComparisons = Math.min(dims.length, rhs.dims.length);

      int index = 0;
      while (retVal == 0 && index < numComparisons) {
        String[] lhsVals = dims[index];
        String[] rhsVals = rhs.dims[index];

        if (lhsVals == null) {
          if (rhsVals == null) {
            ++index;
            continue;
          }
          return -1;
        }

        if (rhsVals == null) {
          return 1;
        }

        retVal = Ints.compare(lhsVals.length, rhsVals.length);

        int valsIndex = 0;
        while (retVal == 0 && valsIndex < lhsVals.length) {
          retVal = lhsVals[valsIndex].compareTo(rhsVals[valsIndex]);
          ++valsIndex;
        }
        ++index;
      }

      if (retVal == 0) {
        return Ints.compare(dims.length, rhs.dims.length);
      }

      return retVal;
    }

    @Override
    public String toString()
    {
      return "TimeAndDims{" +
             "timestamp=" + new DateTime(timestamp) +
             ", dims=" + Lists.transform(
          Arrays.asList(dims), new Function<String[], Object>()
          {
            @Override
            public Object apply(@Nullable String[] input)
            {
              if (input == null || input.length == 0) {
                return Arrays.asList("null");
              }
              return Arrays.asList(input);
            }
          }
      ) + '}';
    }
  }
}


text:
package com.fincatto.nfe310.classes.lote.envio;

import org.junit.Assert;
import org.junit.Test;

public class NFLoteIndicadorProcessamentoTest {

    @Test
    public void deveRepresentarOCodigoCorretamente() {
        Assert.assertEquals("0", NFLoteIndicadorProcessamento.PROCESSAMENTO_ASSINCRONO.getCodigo());
        Assert.assertEquals("1", NFLoteIndicadorProcessamento.PROCESSAMENTO_SINCRONO.getCodigo());
    }

    @Test
    public void deveObterAtravesDoCodigo() {
        Assert.assertEquals(NFLoteIndicadorProcessamento.PROCESSAMENTO_ASSINCRONO, NFLoteIndicadorProcessamento.valueOfCodigo("0"));
        Assert.assertEquals(NFLoteIndicadorProcessamento.PROCESSAMENTO_SINCRONO, NFLoteIndicadorProcessamento.valueOfCodigo("1"));
    }

    @Test
    public void deveRetornarNuloCasoNaoExistaEnumComOCodigo() {
        Assert.assertNull(NFLoteIndicadorProcessamento.valueOfCodigo("2"));
    }

    @Test
    public void deveObterStringficadoCorretamente() {
        Assert.assertEquals("0 - Processamento Ass\u00edncrono", NFLoteIndicadorProcessamento.PROCESSAMENTO_ASSINCRONO.toString());
    }
}

redpajama_set_name: RedPajamaGithub


text:
'use strict';

var strictAgent = require("../index"),
    rest = require("restler"),
    http = require("http"),
    strictAgentLimits = new strictAgent({ maxSockets: 100, queueLimit: 100 }),
    strictAgentTimeouts = new strictAgent({ maxSockets: 100, queueLimit: 200, queueTTL: 1000 }),
    httpAgent = new http.Agent({ maxSockets: 100 }),
    Promise = require("es6-promise").Promise;

var dataToSend = { // just random json
    "nm": "Richard III",
    "cty": "United Kingdom",
    "hse": "House of York",
    "yrs": "1483-1485",
    "extra" : {
        "key": "537b0b35d241d7137654733500c60789537b0b35d241d7137654733500c60789537b0b35d241d7137654733500c60789537b0b35d241d7137654733500c60789537b0b35d241d7137654733500c60789"
    }
}

module.exports = function (server) {
    server.get('/strictedcall', function(req, res, next) {
        return new Promise(function(resolve, reject) {
                rest.get('http://localhost:3000/someslowcall', {
                        agent: strictAgentLimits,
                        data: dataToSend
                    })
                    .on('complete', function(data) {
                        resolve(data);
                    })
                    .on('error', function(err){
                        reject(err);
                    })
                    .on('abort', function(err){
                        reject(err);
                    });
            })
            .then(function(){
                    res.send({'data':'no'});
                    return next();
                }, function(err){
                    console.log('Connection error')
            })
    });
    server.get('/strictedcallttl', function(req, res, next) {
        return new Promise(function(resolve, reject) {
                rest.get('http://localhost:3000/someslowcall', {
                        agent: strictAgentTimeouts,
                        data: dataToSend
                    })
                    .on('complete', function(data) {
                        resolve(data);
                    })
                    .on('error', function(err){
                        reject(err);
                    })
                    .on('abort', function(err){
                        reject(err);
                    });
            })
            .then(function(){
                    res.send({'data':'no'});
                    return next();
                }, function(err){
                    console.log('Connection error')
            })
    });
    server.get('/usualcall', function(req, res, next) {
        return new Promise(function(resolve, reject) {
                rest.get('http://localhost:3000/someslowcall', {
                        agent: httpAgent,
                        data: dataToSend
                    })
                    .on('complete', function(data) {
                        resolve(data);
                    })
                    .on('error', function(err){
                        reject(err);
                    })
                    .on('abort', function(err){
                        reject(err);
                    });
            })
            .then(function(){
                    res.send({'data':'no'});
                    return next();
                }, function(err){
                    console.log('Connection error')
            })
    });
}


text:
<!doctype html>

<title>{% block title %}{% endblock %}</title>

{% for message in get_flashed_messages() %}

{{ message }}

{% endfor %}
{% block content %}
{% endblock %}


text:
<?php
function createRandomPassword() {
	$chars = "003232303232023232023456789";
	srand((double)microtime()*1000000);
	$i = 0;
	$pass = '' ;
	while ($i <= 7) {

		$num = rand() % 33;

		$tmp = substr($chars, $num, 1);

		$pass = $pass . $tmp;

		$i++;

	}
	return $pass;
}
$finalcode='IN-'.createRandomPassword();
?>
<link href="../style.css" media="screen" rel="stylesheet" type="text/css" />

bank Payment

<form action="savesledger.php" method="post">
<input type="hidden" name="name" value="<?php echo $_GET['invoice']; ?>" />
<input type="hidden" name="tot" value="<?php echo $_GET['amount2']; ?>" />

Amount : <input type="text" style="width:265px; height:30px;" name="amount2" />

<input type="hidden" style="width:265px; height:30px;" name="type" value="bank"  />

cheque No: <input type="text" style="width:265px; height:30px;" name="confnumber" required />

 <input id="btn" type="submit" value="save" />

</form>


text:
package org.hippoecm.frontend.editor.plugins.linkpicker;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.jcr.ItemNotFoundException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;

import org.apache.wicket.Session;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IChainingModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.hippoecm.frontend.dialog.AbstractDialog;
import org.hippoecm.frontend.dialog.ClearableDialogLink;
import org.hippoecm.frontend.dialog.IDialogFactory;
import org.hippoecm.frontend.plugin.IPluginContext;
import org.hippoecm.frontend.plugin.config.IPluginConfig;
import org.hippoecm.frontend.service.render.RenderPlugin;
import org.hippoecm.frontend.session.UserSession;
import org.onehippo.forge.selection.frontend.utils.SelectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LinkPickerPlugin extends RenderPlugin<String> {
    private static final long serialVersionUID = 1L;

    static final Logger log = LoggerFactory.getLogger(LinkPickerPlugin.class);

    private static final String EMPTY_LINK_TEXT = "[...]";

    private IModel<String> valueModel;
    @SuppressWarnings("unused")
	private List<String> nodetypes = new ArrayList<String>();

    public LinkPickerPlugin(final IPluginContext context, final IPluginConfig config) {
        super(context, config);

        valueModel = getModel();

        final IModel<String> displayModel = new LoadableDetachableModel<String>() {
            private static final long serialVersionUID = 1L;

            @Override
            protected String load() {
                String docbaseUUID = valueModel.getObject();

                if (docbaseUUID == null || docbaseUUID.equals("") || docbaseUUID.startsWith("cafebabe-")) {
                    return EMPTY_LINK_TEXT;
                }
                try {
                	return ((UserSession) Session.get()).getJcrSession().getNodeByIdentifier(docbaseUUID).getPath();
                } catch (ValueFormatException e) {
                    log.warn("Invalid value format for docbase " + e.getMessage());
                    log.debug("Invalid value format for docbase ", e);
                } catch (PathNotFoundException e) {
                    log.warn("Docbase not found " + e.getMessage());
                    log.debug("Docbase not found ", e);
                } catch (ItemNotFoundException e) {
                    log.info("Docbase not found " + e.getMessage());
                    log.debug("Docbase not found ", e);
                } catch (RepositoryException e) {
                    log.error("Invalid docbase " + e.getMessage(), e);
                }
                return EMPTY_LINK_TEXT;
            }
        };

        //TODO: change constructor of LinkPickerPlugin. Retrieve locale and call linkPickerDialog
        //new constructor with locale parameter
    	final Locale locale = SelectionUtils.getLocale(SelectionUtils.getNode(valueModel));

        if ("edit".equals(config.getString("mode", "view"))) {
            IDialogFactory dialogFactory = new IDialogFactory() {
                private static final long serialVersionUID = 1L;

                public AbstractDialog<String> createDialog() {
                    return new LinkPickerDialog(context, getPluginConfig(), new IChainingModel<String>() {
                        private static final long serialVersionUID = 1L;

                        public String getObject() {
                            return valueModel.getObject();
                        }

                        public void setObject(String object) {
                            valueModel.setObject(object);
                            redraw();
                        }

                        public IModel<String> getChainedModel() {
                            return valueModel;
                        }

                        public void setChainedModel(IModel<?> model) {
                            throw new UnsupportedOperationException("Value model cannot be changed");
                        }

                        public void detach() {
                            valueModel.detach();
                        }

                    }, locale);
                }
            };

            add(new ClearableDialogLink("value", displayModel, dialogFactory, getDialogService()) {
                private static final long serialVersionUID = 1L;

                @Override
                public void onClear() {
                    try {
                        valueModel.setObject(((UserSession) Session.get()).getJcrSession().getRootNode().getIdentifier());
                    } catch (RepositoryException e) {
                        log.error("Unable to reset docbase to rootnode uuid", e);
                    }
                    redraw();
                }

                @Override
                public boolean isClearVisible() {
                    // Checking for string literals ain't pretty. It's probably better to create a better display model.
                    return !EMPTY_LINK_TEXT.equals((String) displayModel.getObject());
                }
            });

        } else {
            add(new Label("value", displayModel));
        }
        setOutputMarkupId(true);
    }

    @Override
    public void onModelChanged() {
        redraw();
    }

}


text:
<?php

/**
 * @see Nani_Exception
 */
require_once 'Nani/Exception.php';

/**
 * @category    Nani
 * @package     Nani_Tree
 * @subpackage  Adapter
 * @copyright   Copyright (c) 2010 Kawika Ohumukini (http://code.google.com/p/naniframework/)
 * @license     http://opensource.org/licenses/bsd-license.php New BSD License
 */
class Nani_Tree_Exception extends Nani_Exception
{}


text:
namespace fileapi {

QuotaReservationManager::QuotaReservationManager(QuotaBackend* backend)
    : backend_(backend),
      weak_ptr_factory_(this) {
  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
}

QuotaReservationManager::~QuotaReservationManager() {
  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
}

void QuotaReservationManager::ReserveQuota(
    const GURL& origin,
    FileSystemType type,
    int64 size,
    const ReserveQuotaCallback& callback) {
  backend_->ReserveQuota(origin, type, size, callback);
}

void QuotaReservationManager::ReleaseReservedQuota(
    const GURL& origin,
    FileSystemType type,
    int64 size) {
  backend_->ReleaseReservedQuota(origin, type, size);
}

void QuotaReservationManager::CommitQuotaUsage(
    const GURL& origin,
    FileSystemType type,
    int64 delta,
    const StatusCallback& callback) {
  backend_->CommitQuotaUsage(origin, type, delta, callback);
}

void QuotaReservationManager::IncreaseDirtyCount(const GURL& origin,
                                                 FileSystemType type) {
  backend_->IncreaseDirtyCount(origin, type);
}

void QuotaReservationManager::DecreaseDirtyCount(const GURL& origin,
                                                 FileSystemType type) {
  backend_->DecreaseDirtyCount(origin, type);
}

scoped_refptr<QuotaReservationBuffer>
QuotaReservationManager::GetReservationBuffer(
    const GURL& origin,
    FileSystemType type) {
  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
  QuotaReservationBuffer** buffer =
      &reservation_buffers_[std::make_pair(origin, type)];
  if (!*buffer) {
    *buffer = new QuotaReservationBuffer(
        weak_ptr_factory_.GetWeakPtr(), origin, type);
  }
  return make_scoped_refptr(*buffer);
}

void QuotaReservationManager::ReleaseReservationBuffer(
    QuotaReservationBuffer* reservation_buffer) {
  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
  std::pair<GURL, FileSystemType> key(reservation_buffer->origin(),
                                      reservation_buffer->type());
  DCHECK_EQ(reservation_buffers_[key], reservation_buffer);
  reservation_buffers_.erase(key);
}

scoped_refptr<QuotaReservation> QuotaReservationManager::CreateReservation(
    const GURL& origin,
    FileSystemType type) {
  return GetReservationBuffer(origin, type)->CreateReservation();;
}

}  // namespace fileapi


text:
const { functions, cmds } = require("./effects")
const {
  basic,
  basicMultistep,
  basicParallel,
  basicMultistepParallel,
  basicEmpty,
  basicMultiArg,
  eitherTestError,
  eitherTestEmpty,
  asyncTest,
  badHandler
} = functions
const { testFn, testFnV2, args } = require("../test")
const { deepEqual } = require("./test-util")

function* singleLine(id) {
  const s1 = yield cmds.httpGet(`http://example.com/api/v1/users/${id}`)
  return s1
}

function* yieldArray() {
  const s1 = yield [{ type: "test" }]
  return s1
}

const testSingleLine = testFn(singleLine)
const testYieldArray = testFn(yieldArray)

function* rethrow() {
  try {
    return yield cmds.echo("foo")
  } catch (e) {
    throw new Error("bar")
  }
}

function* throwFoo() {
  throw new Error("foo")
}

test(
  "testFn should fail if the function error is different than the test error",() => {
    try {
      testFn(throwFoo, () => {
        // prettier-ignore
        return [
          [ [], new Error("bar") ]
        ]
      })()
    } catch (e) {
      deepEqual(e.name, 'Error on Step 1')
      return
    }
    throw new Error("Failed: Did not compare error messages")
  }
)

test(
  "should be able to rethrow errors",
  testFnV2(rethrow, () => {
    // prettier-ignore
    return [
      [],
      [cmds.echo('foo'), new Error("whatever")],
      new Error("bar")
    ]
  })
)

test(
  "testFn should handle undefined returns for semantic test",
  testFn(function*() {}, () => {
    return args().returns()
  })
)

test(
  "testFn should curry",
  testFn(basic)(() => {
    // prettier-ignore
    return [
      [['foo'], cmds.echo('foo')],
      ['foo', 'foo']
    ]
  })
)

test(
  "testFn should pass (basic)",
  testFn(basic, () => {
    // prettier-ignore
    return [
      [['foo'], cmds.echo('foo')],
      ['foo', 'foo']
    ]
  })
)

// Basic

test(
  "testFn semantic should pass (basic)",
  testFn(basic, () => {
    //  prettier-ignore
    return args('foo')
      .yieldCmd(cmds.echo('foo')).yieldReturns('foo')
      .returns('foo')
  })
)

test(
  "testFnV2 should pass (basic)",
  testFnV2(basic, () => {
    // prettier-ignore
    return [
      ['foo'],
      [cmds.echo('foo'), 'foo'],
      'foo'
    ]
  })
)

test(
  "testFn should pass (basicMultiArg)",
  testFn(basicMultiArg, () => {
    // prettier-ignore
    return [
      [['foo', 'bar'], cmds.echo('foobar')],
      ['foobar', 'foobar']
    ]
  })
)

// Basic w multiple arguments

test(
  "testFn should pass (basicMultiArg)",
  testFn(basicMultiArg, () => {
    // prettier-ignore
    return [
      [['foo', 'bar'], cmds.echo('foobar')],
      ['foobar', 'foobar']
    ]
  })
)

test(
  "testFn semantic should pass (basicMultiArg)",
  testFn(basicMultiArg, () => {
    // prettier-ignore
    return args('foo', 'bar')
      .yieldCmd(cmds.echo('foobar')).yieldReturns('foobar')
      .returns('foobar')
  })
)

test(
  "testFn semantic should pass (basicMultiArg)",
  testFnV2(basicMultiArg, () => {
    // prettier-ignore
    return [
      ['foo', 'bar'],
      [cmds.echo('foobar'), 'foobar'],
      'foobar'
    ]
  })
)

//  Basic with multiple steps

test(
  "testFn should pass (basicMultistep)",
  testFn(basicMultistep, () => {
    // prettier-ignore
    return [
      [['foo'], cmds.echo('foo1')],
      ['foo1', cmds.echo('foo2')],
      ['foo2', { s1: 'foo1', s2: 'foo2' }]
    ]
  })
)

test(
  "testFn semantic should pass (basicMultistep)",
  testFn(basicMultistep, () => {
    // prettier-ignore
    return args('foo')
      .yieldCmd(cmds.echo('foo1')).yieldReturns('foo1')
      .yieldCmd(cmds.echo('foo2')).yieldReturns('foo2')
      .returns({ s1: 'foo1', s2: 'foo2' })
  })
)

test(
  "testFnV2 should pass (basicMultistep)",
  testFnV2(basicMultistep, () => {
    // prettier-ignore
    return [
      ['foo'],
      [cmds.echo('foo1'), 'foo1'],
      [cmds.echo('foo2'), 'foo2'],
      { s1: 'foo1', s2: 'foo2' }
    ]
  })
)

// Basic with parallel commands

test(
  "testFn should pass (basicParallel)",
  testFn(basicParallel, () => {
    const c = [cmds.echo("foo"), cmds.echo("foo")]
    // prettier-ignore
    return [
      [['foo'], c],
      [['foo', 'foo'], { s1: 'foo1', s2: 'foo2' }]
    ]
  })
)

test(
  "testFn semantic should pass (basicParallel)",
  testFn(basicParallel, () => {
    const c = [cmds.echo("foo"), cmds.echo("foo")]
    // prettier-ignore
    return args('foo')
      .yieldCmd(c).yieldReturns(['foo', 'foo'])
      .returns({ s1: 'foo1', s2: 'foo2' })
  })
)

test(
  "testFnV2 should pass (basicParallel)",
  testFnV2(basicParallel, () => {
    const c = [cmds.echo("foo"), cmds.echo("foo")]
    // prettier-ignore
    return [
      ['foo'],
      [c, ['foo', 'foo']],
      { s1: 'foo1', s2: 'foo2' }
    ]
  })
)

//  Basic with multiple steps of parallel commands

test(
  "testFn should pass (basicMultistepParallel)",
  testFn(basicMultistepParallel, () => {
    const c1 = [cmds.echo("foo"), cmds.echo("foo")]
    const c2 = [cmds.echo("foo"), cmds.echo("foo")]
    // prettier-ignore
    return [
      [['foo'], c1],
      [['foo', 'foo'], c2],
      [['foo', 'foo'], { s1: 'foo1', s2: 'foo2', s3: 'foo3', s4: 'foo4' }]
    ]
  })
)

test(
  "testFn semantic should pass (basicMultistepParallel)",
  testFn(basicMultistepParallel, () => {
    const c1 = [cmds.echo("foo"), cmds.echo("foo")]
    const c2 = [cmds.echo("foo"), cmds.echo("foo")]
    // prettier-ignore
    return args('foo')
      .yieldCmd(c1).yieldReturns(['foo', 'foo'])
      .yieldCmd(c2).yieldReturns(['foo', 'foo'])
      .returns({ s1: 'foo1', s2: 'foo2', s3: 'foo3', s4: 'foo4' })
  })
)

test(
  "testFnV2 should pass (basicMultistepParallel)",
  testFnV2(basicMultistepParallel, () => {
    const c1 = [cmds.echo("foo"), cmds.echo("foo")]
    const c2 = [cmds.echo("foo"), cmds.echo("foo")]
    // prettier-ignore
    return [
      ['foo'],
      [c1, ['foo', 'foo']],
      [c2, ['foo', 'foo']],
      { s1: 'foo1', s2: 'foo2', s3: 'foo3', s4: 'foo4' }
    ]
  })
)

//  Basic with empty args

test(
  "testFn should pass (basicEmpty)",
  testFn(basicEmpty, () => {
    // prettier-ignore
    return [
      [[null], []],
      [[], []]
    ]
  })
)

test(
  "testFn semantic should pass (basicEmpty)",
  testFn(basicEmpty, () => {
    // prettier-ignore
    return args(null)
      .yieldCmd([]).yieldReturns([])
      .returns([])
  })
)

test(
  "testFnV2 should pass (basicEmpty)",
  testFnV2(basicEmpty, () => {
    // prettier-ignore
    return [
      [null],
      [[], []],
      []
    ]
  })
)

// Either test with error

test(
  "testFn should pass (eitherTestError)",
  testFn(eitherTestError, () => {
    // prettier-ignore
    return [
      [[null], cmds.either(cmds.die('oops'), 'foo')],
      ['foo', 'foo']
    ]
  })
)

test(
  "testFn semantic should pass (eitherTestError)",
  testFn(eitherTestError, () => {
    // prettier-ignore
    return args(null)
      .yieldCmd(cmds.either(cmds.die('oops'), 'foo')).yieldReturns('foo')
      .returns('foo')
  })
)

test(
  "testFnV2 semantic should pass (eitherTestError)",
  testFnV2(eitherTestError, () => {
    // prettier-ignore
    return [
      [null],
      [cmds.either(cmds.die('oops'), 'foo'), 'foo'],
      'foo'
    ]
  })
)

//  Handler error handling

test(
  "testFn should handle errors (badHandler)",
  testFn(badHandler, () => {
    // prettier-ignore
    return [
      [[null], cmds.die('oops')],
      [new Error('oops!'), new Error('oops!')]
    ]
  })
)

test(
  "testFn semantic should handle errors (badHandler)",
  testFn(badHandler, () => {
    // prettier-ignore
    return args([null])
      .yieldCmd(cmds.die('oops')).yieldReturns(new Error('oops!'))
      .returns(new Error('oops!'))
  })
)

test(
  "testFn semantic should handle errors without returns (badHandler)",
  testFn(badHandler, () => {
    // prettier-ignore
    return args([null])
      .yieldCmd(cmds.die('oops')).throws(new Error('oops!'))
  })
)

test(
  "testFnV2 should handle errors (badHandler)",
  testFnV2(badHandler, () => {
    // prettier-ignore
    return [
      [null],
      [cmds.die('oops'), new Error('oops!')],
      new Error('oops!')
    ]
  })
)

// Either test empty

test(
  "testFn should pass (eitherTestEmpty)",
  testFn(eitherTestEmpty, () => {
    // prettier-ignore
    return [
      [[null], cmds.either(cmds.echo(null), 'foo')],
      ['foo', 'foo']
    ]
  })
)

test(
  "testFn semantic should pass (eitherTestEmpty)",
  testFn(eitherTestEmpty, () => {
    // prettier-ignore
    return args(null)
      .yieldCmd(cmds.either(cmds.echo(null), 'foo')).yieldReturns('foo')
      .returns('foo')
  })
)

test(
  "testFnV2 should pass (eitherTestEmpty)",
  testFnV2(eitherTestEmpty, () => {
    // prettier-ignore
    return [
      [null],
      [cmds.either(cmds.echo(null), 'foo'), 'foo'],
      'foo'
    ]
  })
)

//  Async cmd

test(
  "testFn should pass (asyncTest)",
  testFn(asyncTest, () => {
    // prettier-ignore
    return [
      [[null], cmds.async({ type: 'test' })],
      [null, null]
    ]
  })
)

test(
  "testFn semantic should pass (asyncTest)",
  testFn(asyncTest, () => {
    // prettier-ignore
    return args(null)
      .yieldCmd(cmds.async({ type: 'test' })).yieldReturns(null)
      .returns(null)
  })
)

test(
  "testFnV2 should pass (asyncTest)",
  testFnV2(asyncTest, () => {
    // prettier-ignore
    return [
      [null],
      [cmds.async({ type: 'test' }), null],
      null
    ]
  })
)

// Return cmd result
//  Throw from semantic test builder

function* returnCmdResult() {
  return yield cmds.echo("foo")
}

test(
  "testFn semantic should return cmd result",
  testFn(returnCmdResult, () => {
    //  prettier-ignore
    return args()
      .yieldCmd(cmds.echo('foo')).returns("foo")
  })
)

//  Throw from semantic test builder

function* throwSemantic() {
  const value = yield cmds.echo("foo")
  throw new Error("oops")
}

test(
  "testFn semantic should throw if .throws is used",
  testFn(throwSemantic, () => {
    //  prettier-ignore
    return args()
      .yieldCmd(cmds.echo('foo')).yieldReturns('foo')
      .throws(new Error("oops"))
  })
)

test("testFn should throw proper error if function throws incorrect error", () => {
  try {
    testFn(throwSemantic, () => {
      //  prettier-ignore
      return args()
        .yieldCmd(cmds.echo('foo')).yieldReturns('foo')
        .throws(new Error("wrong"))
    })()
  } catch (e) {
    deepEqual(e.name, "Error on Step 2")
    return
  }
  throw new Error("Failed: Did not throw")
})

//  Single line

test(
  "single line should pass",
  testSingleLine(() => {
    //  prettier-ignore
    return [
      [['123'], cmds.httpGet('http://example.com/api/v1/users/123')],
      [{ foo: 'bar' }, { foo: 'bar' }]
    ]
  })
)

test(
  "testFn semantic single line should not fail",
  testSingleLine(() => {
    // prettier-ignore
    return args('123')
      .yieldCmd(cmds.httpGet('http://example.com/api/v1/users/123')).yieldReturns({ foo: 'bar' })
      .returns({ foo: 'bar' })
  })
)

test("testFn should give proper error message if yielding array but no results", () => {
  try {
    testYieldArray(() => {
      //  prettier-ignore
      return [
        [undefined, [{ type: 'test' }]]
      ]
    })()
  } catch (e) {
    deepEqual(e.message, "Your spec does not have as many steps as your function.  Are you missing a return line?")
  }
})

test("testFn should give proper error message if spec is returning undefined", () => {
  try {
    testYieldArray(() => {})()
  } catch (e) {
    deepEqual(e.message, 'Your spec must return an array of tuples.  It is currently returning a value of type "undefined".')
  }
})

test("testFn should give proper error message if spec is returning an object", () => {
  try {
    testYieldArray(() => {
      return {}
    })()
  } catch (e) {
    deepEqual(e.message, 'Your spec must return an array of tuples.  It is currently returning a value of type "object".')
  }
})

test("testFn should give proper error message if spec is returning an string", () => {
  try {
    testYieldArray(() => {
      return "what?"
    })()
  } catch (e) {
    deepEqual(e.message, 'Your spec must return an array of tuples.  It is currently returning a value of type "string".')
  }
})


text:
#include "tickit.h"
#include "hooklists.h"

#include <stdio.h>

#define ROOT_AS_WINDOW(root) ((TickitWindow*)root)
#define WINDOW_AS_ROOT(win)  ((TickitRootWindow*)win)

#define DEBUG_LOGF  if(tickit_debug_enabled) tickit_debug_logf

typedef enum {
  TICKIT_HIERARCHY_INSERT_FIRST,
  TICKIT_HIERARCHY_INSERT_LAST,
  TICKIT_HIERARCHY_REMOVE,
  TICKIT_HIERARCHY_RAISE,
  TICKIT_HIERARCHY_RAISE_FRONT,
  TICKIT_HIERARCHY_LOWER,
  TICKIT_HIERARCHY_LOWER_BACK
} HierarchyChangeType;

struct TickitWindow {
  TickitWindow *parent;
  TickitWindow *first_child;
  TickitWindow *next;
  TickitWindow *focused_child;
  TickitPen *pen;
  TickitRect rect;
  struct {
    int line;
    int col;
    TickitCursorShape shape;
    bool visible;
  } cursor;
  bool is_visible;
  bool is_focused;
  bool steal_input;
  bool focus_child_notify;

  struct TickitEventHook *hooks;
};

#define WINDOW_PRINTF_FMT     "[%dx%d abs@%d,%d]"
#define WINDOW_PRINTF_ARGS(w) (w)->rect.cols, (w)->rect.lines, tickit_window_get_abs_geometry(w).left, tickit_window_get_abs_geometry(w).top

#define RECT_PRINTF_FMT     "[(%d,%d)..(%d,%d)]"
#define RECT_PRINTF_ARGS(r) (r).left, (r).top, tickit_rect_right(&(r)), tickit_rect_bottom(&(r))

DEFINE_HOOKLIST_FUNCS(window,TickitWindow,TickitWindowEventFn)

typedef struct HierarchyChange HierarchyChange;
struct HierarchyChange {
  HierarchyChangeType change;
  TickitWindow *parent;
  TickitWindow *win;
  HierarchyChange *next;
};

typedef struct TickitRootWindow TickitRootWindow;
struct TickitRootWindow {
  TickitWindow win;

  TickitTerm *term;
  TickitRectSet *damage;
  HierarchyChange *hierarchy_changes;
  bool needs_expose;
  bool needs_restore;
  bool needs_later_processing;

  int event_id;
};

static void _request_restore(TickitRootWindow *root);
static void _request_later_processing(TickitRootWindow *root);
static void _request_hierarchy_change(HierarchyChangeType, TickitWindow *);
static void _do_hierarchy_change(HierarchyChangeType change, TickitWindow *parent, TickitWindow *win);
static void _purge_hierarchy_changes(TickitWindow *win);
static int _handle_key(TickitWindow *win, TickitKeyEventInfo *args);
static int _handle_mouse(TickitWindow *win, TickitMouseEventInfo *args);

static int on_term(TickitTerm *term, TickitEventType ev, void *_info, void *user)
{
  TickitRootWindow *root = user;
  TickitWindow *win = ROOT_AS_WINDOW(root);

  if(ev & TICKIT_EV_RESIZE) {
    TickitResizeEventInfo *info = _info;
    int oldlines = win->rect.lines;
    int oldcols  = win->rect.cols;

    tickit_window_resize(win, info->lines, info->cols);
    DEBUG_LOGF("Ir", "Resize to %dx%d",
        info->cols, info->lines);

    if(info->lines > oldlines) {
      TickitRect damage = {
        .top   = oldlines,
        .left  = 0,
        .lines = info->lines - oldlines,
        .cols  = info->cols,
      };
      tickit_window_expose(win, &damage);
    }

    if(info->cols > oldcols) {
      TickitRect damage = {
        .top   = 0,
        .left  = oldcols,
        .lines = oldlines,
        .cols  = info->cols - oldcols,
      };
      tickit_window_expose(win, &damage);
    }
  }

  if(ev & TICKIT_EV_KEY) {
    TickitKeyEventInfo *info = _info;
    static const char * const evnames[] = { NULL, "KEY", "TEXT" };

    DEBUG_LOGF("Ik", "Key event %s %s (mod=%02x)",
        evnames[info->type], info->str, info->mod);

    _handle_key(win, info);
  }

  if(ev & TICKIT_EV_MOUSE) {
    TickitMouseEventInfo *info = _info;
    static const char * const evnames[] = { NULL, "PRESS", "DRAG", "RELEASE", "WHEEL" };

    DEBUG_LOGF("Im", "Mouse event %s %d @%d,%d (mod=%02x)",
        evnames[info->type], info->button, info->col, info->line, info->mod);

    _handle_mouse(win, info);
  }

  return 1;
}

static void init_window(TickitWindow *win, TickitWindow *parent, TickitRect rect)
{
  win->parent = parent;
  win->first_child = NULL;
  win->next = NULL;
  win->focused_child = NULL;
  win->pen = tickit_pen_new();
  win->rect = rect;
  win->cursor.line = 0;
  win->cursor.col = 0;
  win->cursor.shape = TICKIT_CURSORSHAPE_BLOCK;
  win->cursor.visible = true;
  win->is_visible = true;
  win->is_focused = false;
  win->steal_input = false;
  win->focus_child_notify = false;

  win->hooks = NULL;
}

TickitWindow* tickit_window_new_root(TickitTerm *term)
{
  int lines, cols;
  tickit_term_get_size(term, &lines, &cols);

  TickitRootWindow *root = malloc(sizeof(TickitRootWindow));
  if(!root)
    return NULL;

  init_window(ROOT_AS_WINDOW(root), NULL, (TickitRect) { .top = 0, .left = 0, .lines = lines, .cols = cols });

  root->term = term;
  root->hierarchy_changes = NULL;
  root->needs_expose = false;
  root->needs_restore = false;
  root->needs_later_processing = false;

  root->damage = tickit_rectset_new();
  if(!root->damage) {
    tickit_window_destroy(ROOT_AS_WINDOW(root));
    return NULL;
  }

  root->event_id = tickit_term_bind_event(term, TICKIT_EV_RESIZE|TICKIT_EV_KEY|TICKIT_EV_MOUSE,
      &on_term, root);

  tickit_window_expose(ROOT_AS_WINDOW(root), NULL);

  return ROOT_AS_WINDOW(root);
}

static TickitRootWindow *_get_root(const TickitWindow *win)
{
  const TickitWindow *root = win;
  while(root->parent) {
    root = root->parent;
  }
  return WINDOW_AS_ROOT(root);
}

TickitWindow *tickit_window_new(TickitWindow *parent, TickitRect rect, TickitWindowFlags flags)
{
  if(flags & TICKIT_WINDOW_ROOT_PARENT)
    while(parent->parent) {
      rect.top  += parent->rect.top;
      rect.left += parent->rect.left;
      parent = parent->parent;
    }

  TickitWindow *win = malloc(sizeof(TickitWindow));
  if(!win)
    return NULL;

  init_window(win, parent, rect);

  if(flags & TICKIT_WINDOW_HIDDEN)
    win->is_visible = false;
  if(flags & TICKIT_WINDOW_STEAL_INPUT)
    win->steal_input = true;

  _do_hierarchy_change(
    (flags & TICKIT_WINDOW_LOWEST) ? TICKIT_HIERARCHY_INSERT_LAST : TICKIT_HIERARCHY_INSERT_FIRST,
    parent, win
  );

  return win;
}

TickitWindow *tickit_window_parent(const TickitWindow *win)
{
  return win->parent;
}

TickitWindow *tickit_window_root(const TickitWindow *win)
{
  return ROOT_AS_WINDOW(_get_root(win));
}

void tickit_window_destroy(TickitWindow *win)
{
  tickit_hooklist_unbind_and_destroy(win->hooks, win);

  if(win->pen)
    tickit_pen_unref(win->pen);

  for(TickitWindow *child = win->first_child; child; child = child->next)
    tickit_window_destroy(child);

  _purge_hierarchy_changes(win);

  if(win->parent)
    _do_hierarchy_change(TICKIT_HIERARCHY_REMOVE, win->parent, win);

  /* Root cleanup */
  if(!win->parent) {
    TickitRootWindow *root = WINDOW_AS_ROOT(win);
    if(root->damage) {
      tickit_rectset_destroy(root->damage);
    }

    tickit_term_unbind_event_id(root->term, root->event_id);
  }

  free(win);
}

void tickit_window_raise(TickitWindow *win)
{
  _request_hierarchy_change(TICKIT_HIERARCHY_RAISE, win);
}

void tickit_window_raise_to_front(TickitWindow *win)
{
  _request_hierarchy_change(TICKIT_HIERARCHY_RAISE_FRONT, win);
}

void tickit_window_lower(TickitWindow *win)
{
  _request_hierarchy_change(TICKIT_HIERARCHY_LOWER, win);
}

void tickit_window_lower_to_back(TickitWindow *win)
{
  _request_hierarchy_change(TICKIT_HIERARCHY_LOWER_BACK, win);
}

void tickit_window_show(TickitWindow *win)
{
  win->is_visible = true;
  if(win->parent) {
    if(!win->parent->focused_child &&
       (win->focused_child || win->is_focused)) {
      win->parent->focused_child = win;
    }
  }
  tickit_window_expose(win, NULL);
}

void tickit_window_hide(TickitWindow *win)
{
  win->is_visible = false;

  if(win->parent) {
    TickitWindow *parent = win->parent;
    if(parent->focused_child && (parent->focused_child == win)) {
      parent->focused_child = NULL;
    }
    tickit_window_expose(parent, &win->rect);
  }
}

bool tickit_window_is_visible(TickitWindow *win)
{
  return win->is_visible;
}

TickitRect tickit_window_get_geometry(const TickitWindow *win)
{
  return win->rect;
}

TickitRect tickit_window_get_abs_geometry(const TickitWindow *win)
{
  TickitRect geom = win->rect;

  for(win = win->parent; win; win = win->parent)
    tickit_rect_translate(&geom, win->rect.top, win->rect.left);

  return geom;
}

int tickit_window_bottom(const TickitWindow *win)
{
  return win->rect.top + win->rect.lines;
}

int tickit_window_right(const TickitWindow *win)
{
  return win->rect.left + win->rect.cols;
}

void tickit_window_resize(TickitWindow *win, int lines, int cols)
{
  tickit_window_set_geometry(win, (TickitRect){
      .top   = win->rect.top,
      .left  = win->rect.left,
      .lines = lines,
      .cols  = cols}
  );
}

void tickit_window_reposition(TickitWindow *win, int top, int left)
{
  tickit_window_set_geometry(win, (TickitRect){
      .top   = top,
      .left  = left,
      .lines = win->rect.lines,
      .cols  = win->rect.cols}
  );

  if(win->is_focused)
    _request_restore(_get_root(win));
}

void tickit_window_set_geometry(TickitWindow *win, TickitRect geom)
{
  if((win->rect.top != geom.top) ||
     (win->rect.left != geom.left) ||
     (win->rect.lines != geom.lines) ||
     (win->rect.cols != geom.cols))
  {
    TickitGeomchangeEventInfo info = {
      .rect = geom,
      .oldrect = win->rect,
    };

    win->rect = geom;

    run_events(win, TICKIT_EV_GEOMCHANGE, &info);
  }
}

TickitPen *tickit_window_get_pen(const TickitWindow *win)
{
  return win->pen;
}

void tickit_window_set_pen(TickitWindow *win, TickitPen *pen)
{
  if(win->pen)
    tickit_pen_unref(win->pen);

  if(pen)
    win->pen = tickit_pen_ref(pen);
  else
    win->pen = NULL;
}

void tickit_window_expose(TickitWindow *win, const TickitRect *exposed)
{
  TickitRect selfrect = { .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols };
  TickitRect damaged;

  if(exposed) {
    if(!tickit_rect_intersect(&damaged, &selfrect, exposed))
      return;
  }
  else
    damaged = selfrect;

  if(!win->is_visible)
    return;

  if(win->parent) {
    tickit_rect_translate(&damaged, win->rect.top, win->rect.left);
    tickit_window_expose(win->parent, &damaged);
    return;
  }

  DEBUG_LOGF("Wd", "Damage root " RECT_PRINTF_FMT,
      RECT_PRINTF_ARGS(damaged));

  /* If we're here, then we're a root win. */
  TickitRootWindow *root = WINDOW_AS_ROOT(win);
  if(tickit_rectset_contains(root->damage, &damaged))
    return;

  tickit_rectset_add(root->damage, &damaged);

  root->needs_expose = true;
  _request_later_processing(root);
}

static char *_gen_indent(TickitWindow *win)
{
  int depth = 0;
  while(win->parent) {
    depth++;
    win = win->parent;
  }

  static size_t buflen = 0;
  static char *buf = NULL;

  if(depth * 2 >= buflen) {
    free(buf);
    buf = malloc(buflen = (depth * 2 + 1));
    buf[0] = 0;
  }

  for(char *s = buf; depth; depth--, s += 2)
    s[0] = '|', s[1] = ' ';

  return buf;
}

static void _do_expose(TickitWindow *win, const TickitRect *rect, TickitRenderBuffer *rb)
{
  DEBUG_LOGF("Wx", "%sExpose " WINDOW_PRINTF_FMT " " RECT_PRINTF_FMT,
      _gen_indent(win), WINDOW_PRINTF_ARGS(win), RECT_PRINTF_ARGS(*rect));

  if(win->pen)
    tickit_renderbuffer_setpen(rb, win->pen);

  for(TickitWindow* child = win->first_child; child; child = child->next) {
    if(!child->is_visible)
      continue;

    TickitRect exposed;
    if(tickit_rect_intersect(&exposed, rect, &child->rect)) {
      tickit_renderbuffer_save(rb);

      tickit_renderbuffer_clip(rb, &exposed);
      tickit_renderbuffer_translate(rb, child->rect.top, child->rect.left);
      tickit_rect_translate(&exposed, -child->rect.top, -child->rect.left);
      _do_expose(child, &exposed, rb);

      tickit_renderbuffer_restore(rb);
    }

    tickit_renderbuffer_mask(rb, &child->rect);
  }

  TickitExposeEventInfo info = {
    .rect = *rect,
    .rb = rb,
  };
  run_events(win, TICKIT_EV_EXPOSE, &info);
}

static void _request_restore(TickitRootWindow *root)
{
  root->needs_restore = true;
  _request_later_processing(root);
}

static void _request_later_processing(TickitRootWindow *root)
{
  root->needs_later_processing = true;
}

static bool _cell_visible(TickitWindow *win, int line, int col)
{
  TickitWindow *prev;
  while(win) {
    if(line < 0 || line >= win->rect.lines ||
       col  < 0 || col  >= win->rect.cols)
      return false;

    for(TickitWindow *child = win->first_child; child; child = child->next) {
      if(prev && child == prev)
        break;
      if(!child->is_visible)
        continue;

      if(line < child->rect.top  || line >= child->rect.top + child->rect.lines)
        continue;
      if(col  < child->rect.left || col  >= child->rect.left + child->rect.cols)
        continue;

      return false;
    }

    line += win->rect.top;
    col  += win->rect.left;

    prev = win;
    win = win->parent;
  }

  return true;
}

static void _do_restore(TickitRootWindow *root)
{
  TickitWindow *win = ROOT_AS_WINDOW(root);

  while(win) {
    if(!win->is_visible)
      break;

    if(!win->focused_child)
      break;

    win = win->focused_child;
  }

  if(win && win->is_focused && win->cursor.visible &&
     _cell_visible(win, win->cursor.line, win->cursor.col)) {
    tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORVIS, 1);
    TickitRect abs_geom = tickit_window_get_abs_geometry(win);
    int cursor_line = win->cursor.line + abs_geom.top;
    int cursor_col = win->cursor.col + abs_geom.left;
    tickit_term_goto(root->term, cursor_line, cursor_col);
    tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORSHAPE, win->cursor.shape);
  }
  else
    tickit_term_setctl_int(root->term, TICKIT_TERMCTL_CURSORVIS, 0);

  tickit_term_flush(root->term);
}

void tickit_window_flush(TickitWindow *win)
{
  if(win->parent)
    // Can't flush non-root.
    return;

  TickitRootWindow *root = WINDOW_AS_ROOT(win);
  if(!root->needs_later_processing)
    return;

  root->needs_later_processing = false;

  if(root->hierarchy_changes) {
    HierarchyChange *req = root->hierarchy_changes;
    while(req) {
      _do_hierarchy_change(req->change, req->parent, req->win);
      HierarchyChange *next = req->next;
      free(req);
      req = next;
    }
    root->hierarchy_changes = NULL;
  }

  if(root->needs_expose) {
    root->needs_expose = false;

    TickitWindow *root_window = ROOT_AS_WINDOW(root);
    TickitRenderBuffer *rb = tickit_renderbuffer_new(root_window->rect.lines, root_window->rect.cols);

    int damage_count = tickit_rectset_rects(root->damage);
    TickitRect *rects = malloc(damage_count * sizeof(TickitRect));
    tickit_rectset_get_rects(root->damage, rects, damage_count);

    tickit_rectset_clear(root->damage);

    for(int i = 0; i < damage_count; i++) {
      TickitRect *rect = &rects[i];
      tickit_renderbuffer_save(rb);
      tickit_renderbuffer_clip(rb, rect);
      _do_expose(root_window, rect, rb);
      tickit_renderbuffer_restore(rb);
    }

    free(rects);

    tickit_renderbuffer_flush_to_term(rb, root->term);
    tickit_renderbuffer_destroy(rb);

    root->needs_restore = true;
  }

  if(root->needs_restore) {
    root->needs_restore = false;
    _do_restore(root);
  }
}

static TickitWindow **_find_child(TickitWindow *parent, TickitWindow *win)
{
  TickitWindow **winp = &parent->first_child;
  while(*winp && *winp != win)
    winp = &(*winp)->next;

  return winp;
}

static void _do_hierarchy_insert_first(TickitWindow *parent, TickitWindow *win)
{
  win->next = parent->first_child;
  parent->first_child = win;
}

static void _do_hierarchy_insert_last(TickitWindow *parent, TickitWindow *win)
{
  TickitWindow **lastp = &parent->first_child;
  while(*lastp)
    lastp = &(*lastp)->next;

  *lastp = win;
  win->next = NULL;
}

static void _do_hierarchy_remove(TickitWindow *parent, TickitWindow *win)
{
  TickitWindow **winp = _find_child(parent, win);
  if(!winp)
    return;

  *winp = (*winp)->next;
  win->next = NULL;
}

static void _do_hierarchy_raise(TickitWindow *parent, TickitWindow *win)
{
  TickitWindow **prevp = &parent->first_child;
  if(*prevp == win) // already first
    return;

  while(*prevp && (*prevp)->next != win)
    prevp = &(*prevp)->next;

  if(!prevp) // not found
    return;

  TickitWindow *after  = win->next;

  win->next = *prevp;
  (*prevp)->next = after;
  *prevp = win;
}

static void _do_hierarchy_lower(TickitWindow *parent, TickitWindow *win)
{
  TickitWindow **winp = _find_child(parent, win);
  if(!winp) // not found
    return;

  TickitWindow *after = win->next;
  if(!after) // already last
    return;

  win->next = after->next;
  *winp = after;
  after->next = win;
}

static void _do_hierarchy_change(HierarchyChangeType change, TickitWindow *parent, TickitWindow *win)
{
  const char *fmt = NULL;

  switch(change) {
    case TICKIT_HIERARCHY_INSERT_FIRST:
       fmt = "Window " WINDOW_PRINTF_FMT " adds " WINDOW_PRINTF_FMT;
      _do_hierarchy_insert_first(parent, win);
      break;
    case TICKIT_HIERARCHY_INSERT_LAST:
      fmt = "Window " WINDOW_PRINTF_FMT " adds " WINDOW_PRINTF_FMT;
      _do_hierarchy_insert_last(parent, win);
      break;
    case TICKIT_HIERARCHY_REMOVE:
      fmt = "Window " WINDOW_PRINTF_FMT " removes " WINDOW_PRINTF_FMT;
      _do_hierarchy_remove(parent, win);
      if(parent->focused_child && parent->focused_child == win)
        parent->focused_child = NULL;
      break;
    case TICKIT_HIERARCHY_RAISE:
      fmt = "Window " WINDOW_PRINTF_FMT " raises " WINDOW_PRINTF_FMT;
      _do_hierarchy_raise(parent, win);
      break;
    case TICKIT_HIERARCHY_RAISE_FRONT:
      fmt = "Window " WINDOW_PRINTF_FMT " raises " WINDOW_PRINTF_FMT " to front";
      _do_hierarchy_remove(parent, win);
      _do_hierarchy_insert_first(parent, win);
      break;
    case TICKIT_HIERARCHY_LOWER:
      fmt = "Window " WINDOW_PRINTF_FMT " lowers " WINDOW_PRINTF_FMT;
      _do_hierarchy_lower(parent, win);
      break;
    case TICKIT_HIERARCHY_LOWER_BACK:
      fmt = "Window " WINDOW_PRINTF_FMT " lowers " WINDOW_PRINTF_FMT " to back";
      _do_hierarchy_remove(parent, win);
      _do_hierarchy_insert_last(parent, win);
      break;
  }

  if(fmt)
    DEBUG_LOGF("Wh", fmt,
        WINDOW_PRINTF_ARGS(parent), WINDOW_PRINTF_ARGS(win));

  tickit_window_expose(parent, &win->rect);
}

static void _request_hierarchy_change(HierarchyChangeType change, TickitWindow *win)
{
  if(!win->parent)
    /* Can't do anything to the root win */
    return;

  HierarchyChange *req = malloc(sizeof(HierarchyChange));
  req->change = change;
  req->parent = win->parent;
  req->win = win;
  req->next = NULL;
  TickitRootWindow *root = _get_root(win);
  if(!root->hierarchy_changes) {
    root->hierarchy_changes = req;
    _request_later_processing(root);
  }
  else {
    HierarchyChange *chain = root->hierarchy_changes;
    while(chain->next) {
      chain = chain->next;
    }
    chain->next = req;
  }
}

static void _purge_hierarchy_changes(TickitWindow *win)
{
  TickitRootWindow *root = _get_root(win);
  HierarchyChange **changep = &root->hierarchy_changes;
  while(*changep) {
    HierarchyChange *req = *changep;
    if(req->parent == win || req->win == win) {
      *changep = req->next;
      free(req);
    }
    else
      changep = &req->next;
  }
}

static bool _scrollrectset(TickitWindow *win, TickitRectSet *visible, int downward, int rightward, TickitPen *pen)
{
  TickitWindow *origwin = win;

  int abs_top = 0, abs_left = 0;

  while(win) {
    if(!win->is_visible)
      return false;

    tickit_pen_copy(pen, win->pen, false);

    TickitWindow *parent = win->parent;
    if(!parent)
      break;

    abs_top  += win->rect.top;
    abs_left += win->rect.left;
    tickit_rectset_translate(visible, win->rect.top, win->rect.left);

    for(TickitWindow *sib = parent->first_child; sib; sib = sib->next) {
      if(sib == win)
        break;
      if(!sib->is_visible)
        continue;

      tickit_rectset_subtract(visible, &sib->rect);
    }

    win = parent;
  }

  TickitTerm *term = WINDOW_AS_ROOT(win)->term;

  int n = tickit_rectset_rects(visible);
  TickitRect rects[n];
  tickit_rectset_get_rects(visible, rects, n);

  bool ret = true;
  bool done_pen = false;

  for(int i = 0; i < n; i++) {
    TickitRect rect = rects[i];

    TickitRect origrect = rect;
    tickit_rect_translate(&origrect, -abs_top, -abs_left);

    if(abs(downward) >= rect.lines || abs(rightward) >= rect.cols) {
      tickit_window_expose(origwin, &origrect);
      continue;
    }

    // TODO: This may be more efficiently done with some rectset operations
    //   instead of completely resetting and rebuilding the set
    TickitRectSet *damageset = WINDOW_AS_ROOT(win)->damage;
    int n_damage = tickit_rectset_rects(damageset);
    TickitRect damage[n_damage];
    tickit_rectset_get_rects(damageset, damage, n_damage);
    tickit_rectset_clear(damageset);

    for(int j = 0; j < n_damage; j++) {
      TickitRect r = damage[j];

      if(tickit_rect_bottom(&r) < rect.top || r.top > tickit_rect_bottom(&rect) ||
         tickit_rect_right(&r) < rect.left || r.left > tickit_rect_right(&rect)) {
        tickit_rectset_add(damageset, &r);
        continue;
      }

      TickitRect outside[4];
      int n_outside;
      if((n_outside = tickit_rect_subtract(outside, &r, &rect))) {
        for(int k = 0; k < n_outside; k++)
          tickit_rectset_add(damageset, outside+k);
      }

      TickitRect inside;
      if(tickit_rect_intersect(&inside, &r, &rect)) {
        tickit_rect_translate(&inside, -downward, -rightward);
        if(tickit_rect_intersect(&inside, &inside, &rect))
          tickit_rectset_add(damageset, &inside);
      }
    }

    DEBUG_LOGF("Wsr", "Term scrollrect " RECT_PRINTF_FMT " by %+d,%+d",
      RECT_PRINTF_ARGS(rect), rightward, downward);

    if(!done_pen) {
      // TODO: only bg matters
      tickit_term_setpen(term, pen);
      done_pen = true;
    }

    if(tickit_term_scrollrect(term, rect, downward, rightward)) {
      if(downward > 0) {
        // "scroll down" means lines moved upward, so the bottom needs redrawing
        tickit_window_expose(origwin, &(TickitRect){
            .top  = origrect.top + origrect.lines - downward, .lines = downward,
            .left = origrect.left,                            .cols  = rect.cols
        });
      }
      else if(downward < 0) {
        // "scroll up" means lines moved downward, so top needs redrawing
        tickit_window_expose(origwin, &(TickitRect){
            .top  = origrect.top,  .lines = -downward,
            .left = origrect.left, .cols  = rect.cols,
        });
      }

      if(rightward > 0) {
        // "scroll right" means columns moved leftward, so the right edge needs redrawing
        tickit_window_expose(origwin, &(TickitRect){
            .top  = origrect.top,                              .lines = rect.lines,
            .left = origrect.left + origrect.cols - rightward, .cols  = rightward,
        });
      }
      else if(rightward < 0) {
        tickit_window_expose(origwin, &(TickitRect){
            .top  = origrect.top,  .lines = rect.lines,
            .left = origrect.left, .cols  = -rightward,
        });
      }
    }
    else {
      tickit_window_expose(origwin, &origrect);
      ret = false;
    }
  }

  return ret;
}

static bool _scroll(TickitWindow *win, const TickitRect *origrect, int downward, int rightward, TickitPen *pen, bool mask_children)
{
  TickitRect rect;
  TickitRect selfrect = { .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols };

  if(!tickit_rect_intersect(&rect, &selfrect, origrect))
    return false;

  DEBUG_LOGF("Ws", "Scroll " RECT_PRINTF_FMT " by %+d,%+d",
    RECT_PRINTF_ARGS(rect), rightward, downward);

  if(pen)
    pen = tickit_pen_ref(pen);
  else
    pen = tickit_pen_new();

  TickitRectSet *visible = tickit_rectset_new();

  tickit_rectset_add(visible, &rect);

  if(mask_children)
    for(TickitWindow *child = win->first_child; child; child = child->next) {
      if(!child->is_visible)
        continue;
      tickit_rectset_subtract(visible, &child->rect);
    }

  bool ret = _scrollrectset(win, visible, downward, rightward, pen);

  tickit_rectset_destroy(visible);
  tickit_pen_unref(pen);

  return ret;
}

bool tickit_window_scrollrect(TickitWindow *win, const TickitRect *rect, int downward, int rightward, TickitPen *pen)
{
  return _scroll(win, rect, downward, rightward, pen, true);
}

bool tickit_window_scroll(TickitWindow *win, int downward, int rightward)
{
  return _scroll(win,
    &((TickitRect){ .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols }),
    downward, rightward, NULL, true);
}

bool tickit_window_scroll_with_children(TickitWindow *win, int downward, int rightward)
{
  return _scroll(win,
    &((TickitRect){ .top = 0, .left = 0, .lines = win->rect.lines, .cols = win->rect.cols }),
    downward, rightward, NULL, false);
}

void tickit_window_set_cursor_position(TickitWindow *win, int line, int col)
{
  win->cursor.line = line;
  win->cursor.col = col;

  if(win->is_focused)
    _request_restore(_get_root(win));
}

void tickit_window_set_cursor_visible(TickitWindow *win, bool visible)
{
  win->cursor.visible = visible;

  if(win->is_focused)
    _request_restore(_get_root(win));
}

void tickit_window_set_cursor_shape(TickitWindow *win, TickitCursorShape shape)
{
  win->cursor.shape = shape;

  if(win->is_focused)
    _request_restore(_get_root(win));
}

static void _focus_gained(TickitWindow *win, TickitWindow *child);
static void _focus_lost(TickitWindow *win);

void tickit_window_take_focus(TickitWindow *win)
{
  _focus_gained(win, NULL);
}

static void _focus_gained(TickitWindow *win, TickitWindow *child)
{
  if(win->focused_child && child && win->focused_child != child)
    _focus_lost(win->focused_child);

  if(win->parent) {
    if(win->is_visible)
      _focus_gained(win->parent, win);
  }
  else
    _request_restore(_get_root(win));

  if(!child) {
    win->is_focused = true;

    TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_IN, .win = win };
    run_events(win, TICKIT_EV_FOCUS, &info);
  }
  else if(win->focus_child_notify) {
    TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_IN, .win = child };
    run_events(win, TICKIT_EV_FOCUS, &info);
  }

  win->focused_child = child;
}

static void _focus_lost(TickitWindow *win)
{
  if(win->focused_child) {
    _focus_lost(win->focused_child);

    if(win->focus_child_notify) {
      TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_OUT, .win = win->focused_child };
      run_events(win, TICKIT_EV_FOCUS, &info);
    }
  }

  if(win->is_focused) {
    win->is_focused = false;
    TickitFocusEventInfo info = { .type = TICKIT_FOCUSEV_OUT, .win = win };
    run_events(win, TICKIT_EV_FOCUS, &info);
  }
}

bool tickit_window_is_focused(const TickitWindow *win)
{
  return win->is_focused;
}

void tickit_window_set_focus_child_notify(TickitWindow *win, bool notify)
{
  win->focus_child_notify = notify;
}

static int _handle_key(TickitWindow *win, TickitKeyEventInfo *info)
{
  if(!win->is_visible)
    return 0;

  if(win->first_child && win->first_child->steal_input)
    if(_handle_key(win->first_child, info))
      return 1;

  if(win->focused_child)
    if(_handle_key(win->focused_child, info))
      return 1;

  if(run_events_whilefalse(win, TICKIT_EV_KEY, info))
    return 1;

  // Last-ditch attempt to spread it around other children
  for(TickitWindow *child = win->first_child; child; child = child->next) {
    if(child == win->focused_child)
      continue;

    if(_handle_key(child, info))
      return 1;
  }

  return 0;
}

static int _handle_mouse(TickitWindow *win, TickitMouseEventInfo *info)
{
  if(!win->is_visible)
    return 0;

  for(TickitWindow *child = win->first_child; child; child = child->next) {
    int child_line = info->line - child->rect.top;
    int child_col  = info->col  - child->rect.left;

    if(!child->steal_input) {
      if(child_line < 0 || child_line >= child->rect.lines)
        continue;
      if(child_col < 0 || child_col >= child->rect.cols)
        continue;
    }

    TickitMouseEventInfo childinfo = *info;
    childinfo.line = child_line;
    childinfo.col  = child_col;

    if(_handle_mouse(child, &childinfo))
      return 1;
  }

  if(run_events_whilefalse(win, TICKIT_EV_MOUSE, info))
    return 1;

  return 0;
}


text:
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                           handler:^(NSEvent *event){
        NSDate* now = [[NSDate alloc] init];
        NSDateFormatter* format = [[NSDateFormatter alloc] init];
        [format setDateFormat:@"c,yy,MM,dd,HH,mm,ss,Z"];

        // Find the currently running app.
        NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication];
        NSArray* runningApps = [[NSWorkspace sharedWorkspace] runningApplications];
        NSString* appName = [app localizedName];
        NSString* update = [NSString stringWithFormat: @"%d,%@,'%@'\n",
                event.keyCode, [format stringFromDate: now], appName];
        FILE* f = fopen([[NSHomeDirectory() stringByAppendingString: @"/.keys"]
                                                            UTF8String], "a");
        fprintf(f, "%s", [update UTF8String]);
        fclose(f);

        [now release];
        [format release];
    }];
}

@end

int main(int argc, char *argv[])
{
    AppDelegate *delegate = [[AppDelegate alloc] init];
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSApplication *app = [NSApplication sharedApplication];

    [app setDelegate:delegate];
    [NSApp run];

    [pool drain];
    [delegate release];
    return 0;
}


text:
<?php

namespace wechat\controllers;

use wechat\models\Wechat;
use Yii;
use yii\base\Exception;
use yii\web\Controller;
use wechat\models\User;
use wechat\models\Res;
use wechat\helpers\Wx;

class WechatController extends Controller{

    public $wechat;

    public $request;

    public $enableCsrfValidation = false;

    //用户消息类别
    protected $typeMsg = ['text', 'image', 'voice','video', 'location','link'];

    //事件类别
    protected $typeEvent = [
        'subscribe' => 'eventSubscribe',
        'unsubscribe'=> 'eventUnSubscribe',
        'SCAN' => 'eventScan',
        'LOCATION' => 'eventLocation',
        'CLICK' => 'eventClick',
        'VIEW' => 'eventView'
    ];

    /**
     * @title 对消息进行验证
     */
    public function beforeAction($action='')
    {
        parent::beforeAction($action);
        $echoStr = Yii::$app->request->get('echostr');
        if($this->checkSignature()){
            echo $echoStr;
            return true;
        }
    }
    /**
     * @title 微信反回的信息在此处理
     */
    public function actionIndex()
    {

        //获取post数据
        $HTTP_RAW_POST_DATA = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : file_get_contents("php://input");

        if(!!$HTTP_RAW_POST_DATA) {
            $this->request = (array)simplexml_load_string($HTTP_RAW_POST_DATA, 'SimpleXMLElement', LIBXML_NOCDATA);

            // 响应消息
            if (in_array($this->request['MsgType'], $this->typeMsg))
                return $this->responseMsg();

            // 响应事件
            if ($this->request['MsgType'] == 'event' )
                return $this->responseEvent();
        }
    }

    /**
     * @title 处理消息
     */
    public function responseMsg()
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token='.Wechat::getAccessToken();
        $data = Wx::activeText($this->request['FromUserName'], '主动消息');
        Wx::postJson($url, $data);
    }

    /**
     * @title 处理事件
     */
    public function responseEvent()
    {
        $event = $this->request['Event'];
        if (array_key_exists($event, $this->typeEvent)) {
            $method = $this->typeEvent[$event];
            $this->$method();
        }
    }

    /**
     * @title 关注
     */
    public function eventSubscribe()
    {
        $user_info = Wx::getUserInfo($this->request('FromUserName'));
        $model = new User();
        $model->setAttributes($user_info);

        if ($model->validate() && $model->save()) {
            echo Wx::lazyText($this->request['ToUserName'], $this->request['FromUserName'], '谢谢');
        }
    }

    /**
     * @title 取消关注
     */
    public function eventUnSubscribe()
    {
        $model = User::findOne($this->request['FromUserName']);
        if (!$model) return;
        $model->subscribe = 0;
        if ($model->save()) {
            return true;
        }
    }

    /**
     * @title 扫描
     */
    public function eventScan()
    {

    }

    /**
     * @title 上报位置
     */
    public function eventLocation()
    {

    }

    /**
     * @title 点击拉取事件
     */
    public function eventClick()
    {
        $key = $this->request['EventKey'];
        $key_type = substr($key, 0, strpos($key, '_'));

        if (strtoupper($key_type) == 'NEWS') {
            $this->menuEventNews();
        } else {
            $this->menuEvent($key_type);
        }
    }

    /**
     * @title 菜单图文事件
     * @param $key
     *
     */
    private function menuEventNews()
    {
        $key = $this->request['EventKey'];
        $list = Res::find()->where(['event_key' => $key])->limit(2)->asArray()->all();
        echo Wx::lazyNews($this->request['ToUserName'], $this->request['FromUserName'], $list);
    }

    /**
     * @title 其它菜单事件
     */
    private function menuEvent($key_type)
    {
        $key = $this->request['EventKey'];
        $method = 'lazy'.$key_type;
        $res = Res::find()->where(['event_key'=>$key])->one();
        if(!array_search(strtoupper($key_type), ['NEWS', 'TEXT'])) {
            $res = $res->setMediaId();
        }
        echo Wx::$method($this->request['ToUserName'], $this->request['FromUserName'], $res->attributes);
    }

    /**
     * @title 点击跳转
     */
    public function eventView()
    {

    }

    /**
     * @title 验证方法
     * @return bool
     * @throws Exception
     */
    private function checkSignature()
    {

        $module = \Yii::$app->controller->module;

        $conf = $module->params['wechat'];
        $token = $conf['token'];
        if (!$token) {
            throw new Exception('token is not defined!');
        }

        $query = Yii::$app->request->get();

        !empty($query['signature']) &&  $signature = $query['signature'];
        !empty($query["timestamp"]) &&  $timestamp = $query["timestamp"];
        !empty($query["nonce"]) && $nonce = $query["nonce"];

        if (!isset($signature) || !isset($timestamp) || !isset($nonce)) {
            throw new Exception('Missing parameter !');
        }

        $tmpArr = array($token, $timestamp, $nonce);

        sort($tmpArr, SORT_STRING);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );

        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }

}


text:
<?php

declare(strict_types=1);

namespace ProophExample\PrototypingFlavour\Messaging;

use Prooph\EventMachine\EventMachine;
use Prooph\EventMachine\EventMachineDescription;
use Prooph\EventMachine\JsonSchema\JsonSchema;

final class CommandWithCustomHandler implements EventMachineDescription
{
    public const CMD_DO_NOTHING = 'DoNothing';
    public const NO_OP_HANDLER = 'NoOpHandler';

    public static function describe(EventMachine $eventMachine): void
    {
        $eventMachine->registerCommand(self::CMD_DO_NOTHING, JsonSchema::object(['msg' => JsonSchema::string()]));
        $eventMachine->preProcess(self::CMD_DO_NOTHING, self::NO_OP_HANDLER);
    }
}


text:
import argparse
import filecmp
import json
import os
from pathlib import Path
import shutil
from zipfile import ZipFile

import difflib
from contextlib import redirect_stdout
import dictdiffer

from ruamel.yaml import YAML
from slack_sdk import WebClient

yaml = YAML()

SKIPPED_FILES = {"signatures.sf", "script-CommonServerPython.yml"}

def sort_dict(dct: dict):
    for k, v in dct.items():
        if isinstance(v, dict):
            sort_dict(v)
        if isinstance(v, list):
            try:
                v.sort()
            except TypeError:
                v.sort(key=lambda item: item.get("name"))

def compare_indexes(index_id_set_path: Path, index_graph_path: Path, output_path: Path) -> bool:
    with index_id_set_path.open() as f1, index_graph_path.open() as f2:
        index_id_set = json.load(f1)
        index_graph = json.load(f2)
        index_id_set.pop("modified", None)
        index_graph.pop("modified", None)
        sort_dict(index_id_set)
        sort_dict(index_graph)
        diff_list = list(dictdiffer.diff(index_id_set, index_graph))
    if diff_list:
        with (output_path / "index-diff.json").open("w") as output:
            json.dump(diff_list, output, indent=4)
        shutil.copyfile(index_id_set_path, output_path / "index-id_set.json")
        shutil.copyfile(index_graph_path, output_path / "index-graph.json")
        return True
    return False

def compare_dirs(dir1: str, dir2: str, output_path: Path) -> list[str]:
    dir_compare = filecmp.dircmp(dir1, dir2)
    full_report_path = output_path / "full_report.log"
    with open(full_report_path, "w") as f:
        with redirect_stdout(f):
            dir_compare.report_full_closure()
    diff_files: list[str] = []
    compare_files(dir_compare, dir1, dir2, output_path, diff_files)
    return diff_files

def compare_zips(zip1: Path, zip2: Path, output_path: Path) -> list[str]:
    """Compare two zip files content"""
    # extract zip files
    output_path.mkdir(parents=True, exist_ok=True)
    zip1_files = str(output_path / "id_set_tmp")
    zip2_files = str(output_path / "graph_tmp")
    with ZipFile(zip1, "r") as zip1_content, ZipFile(zip2, "r") as zip2_content:
        # get the list of files in the zip files
        zip1_content.extractall(path=zip1_files)
        zip2_content.extractall(path=zip2_files)

    # compare the directories
    return compare_dirs(zip1_files, zip2_files, output_path)

def compare_files(
    dir_compare: filecmp.dircmp[str], zip1_files: str, zip2_files: str, output_path: Path, diff_files: list[str]
):
    for file in dir_compare.common_files:
        if file not in dir_compare.same_files and file not in SKIPPED_FILES:
            file_diff(output_path, zip1_files, zip2_files, file, diff_files)
    for subdir in dir_compare.subdirs.values():
        compare_files(subdir, subdir.left, subdir.right, output_path / Path(subdir.left).name, diff_files)

def file_diff_text(file1_path: Path, file2_path: Path, output_path_file: Path):
    output_path_file.unlink(missing_ok=True)

    with output_path_file.open("w") as f:
        with open(file1_path) as f1, open(file2_path) as f2:
            f1lines = f1.readlines()
            f2lines = f2.readlines()
            d = difflib.Differ()
            diffs = [x for x in d.compare(f1lines, f2lines) if x[0] in ("+", "-")]
            if diffs:
                f.writelines(diffs)
                return True
    return False

def remove_known_diffs(dct1: dict, dct2: dict, known_diff: list[str]):
    for diff in known_diff:
        dct1.pop(diff, None)
        dct2.pop(diff, None)

def file_diff(output_path: Path, zip1_files: str, zip2_files: str, file: str, diff_files: list[str]):
    output_path.mkdir(exist_ok=True, parents=True)
    try:
        file1_path = Path(zip1_files) / file
        file2_path = Path(zip2_files) / file
        file_diff_text(file1_path, file2_path, output_path / f"{file}-textdiff.log")
        if file1_path.suffix == ".yml":
            load_func = yaml.load
        elif file1_path.suffix == ".json":
            load_func = json.load  # type: ignore[assignment]
        else:
            print(f"not yaml or json: {output_path / file}. continue")
            return
        output_dict_diff = output_path / f"{file}-dictdiff.json"
        output_dict_diff.unlink(missing_ok=True)
        with open(output_dict_diff, "w") as f:
            with open(file1_path) as f1, open(file2_path) as f2:
                dct1 = load_func(f1)
                dct2 = load_func(f2)
                remove_known_diffs(dct1, dct2, ["updated", "downloads"])
                if file == "metadata.json":
                    sort_dict(dct1)
                    sort_dict(dct2)
                diff_found = list(dictdiffer.diff(dct1, dct2))
                if diff_found:
                    json.dump(diff_found, f, indent=4)
                    shutil.copyfile(file1_path, output_path / f"id-set-{file1_path.name}")
                    shutil.copyfile(file2_path, output_path / f"graph-{file2_path.name}")
                    diff_files.append(file1_path.name)

    except Exception as e:
        print(f"could not diff files {file1_path}:{file2_path}: {e}")

def compare(
    marketplace: str,
    zip_id_set: Path,
    zip_graph: Path,
    index_id_set_path: Path,
    index_graph_path: Path,
    collected_packs_id_set: Path,
    collected_packs_graph: Path,
    message: list[str],
    output_path: Path,
):
    diff_found = False
    output_path.mkdir(exist_ok=True, parents=True)
    # compare directories
    dir_cmp = filecmp.dircmp(zip_id_set, zip_graph)
    dir_cmp.report_full_closure()
    for file in dir_cmp.common_files:
        pack = file.removesuffix(".zip")
        if diff_files := compare_zips(zip_id_set / file, zip_graph / file, output_path / pack):
            diff_found = True
            message.append(f'Detected differences in the following files for pack {pack}: {", ".join(diff_files)}')
    if compare_indexes(index_id_set_path, index_graph_path, output_path):
        diff_found = True
        message.append("Detected differences between index.json files")
    if file_diff_text(collected_packs_id_set, collected_packs_graph, output_path / "collect_tests_diff.log"):
        diff_found = True
        message.append("Detected differences between collect tests results")
        shutil.copy(collected_packs_id_set, output_path / "collected_packs-id_set.txt")
        shutil.copy(collected_packs_graph, output_path / "collected_packs-graph.txt")

    shutil.make_archive(str(output_path / f"diff-{marketplace}"), "zip", output_path)

    if not diff_found:
        message.append("No difference were found!")
    return message

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--artifacts", help="artifacts of the build")
    parser.add_argument("--marketplace", "--mp", help="Marketplace to use")
    parser.add_argument("--output-path", help="Output path")
    parser.add_argument("--slack-token", "-s", help="Slack token", required=False)

    args = parser.parse_args()
    artifacts = Path(args.artifacts)
    output_path = Path(args.output_path)
    slack_token = args.slack_token
    marketplace = args.marketplace

    zip_id_set = artifacts / "uploaded_packs-id_set"
    zip_graph = artifacts / "uploaded_packs-graph"

    index_id_set_path = artifacts / "index.json"
    index_graph_path = artifacts / "index-graph.json"

    collected_packs_id_set = artifacts / "content_packs_to_install.txt"
    collected_packs_graph = artifacts / "content_packs_to_install-graph.txt"

    message = [
        f"Diff report for {marketplace}",
        f'Job URL: {os.getenv("CI_JOB_URL")}',
    ]
    if not zip_graph.exists():
        message.append("No packs were uploaded for id_set")
    if not zip_id_set.exists():
        message.append("No packs were uploaded for graph")

    else:
        message = compare(
            marketplace,
            zip_id_set,
            zip_graph,
            index_id_set_path,
            index_graph_path,
            collected_packs_id_set,
            collected_packs_graph,
            message,
            output_path,
        )
    if slack_token:
        slack_client = WebClient(token=slack_token)
        diff_output = output_path / f"diff-{marketplace}.zip"
        if not diff_output.exists():
            slack_client.chat_postMessage(
                channel="dmst-graph-tests",
                text="\n".join(message),
            )
        else:
            slack_client.files_upload(
                file=str(diff_output),
                channels="dmst-graph-tests",
                initial_comment="\n".join(message),
            )

if __name__ == "__main__":
    main()


text: * Change dependency to RMB v11.0.0. Schema now disallows additional properties.