title: “Imporatnt analysis scripts using seurat object” output: html_notebook Author:Upasna Srivastava

sce <- as.SingleCellExperiment(seurat)
reducedDim(sce, 'PCA_sub') <- reducedDim(sce, 'PCA')[,1:15, drop = FALSE]

ass_prob <- bootstrapCluster(sce, FUN = function(x) {
    g <- buildSNNGraph(x, use.dimred = 'PCA_sub')
    igraph::cluster_walktrap(g)$membership
  },
  clusters = sce$seurat_clusters
)

p <- ass_prob %>%
  as_tibble() %>%
  rownames_to_column(var = 'cluster_1') %>%
  pivot_longer(
    cols = 2:ncol(.),
    names_to = 'cluster_2',
    values_to = 'probability'
  ) %>%
  mutate(
    cluster_1 = as.character(as.numeric(cluster_1) - 1),
    cluster_1 = factor(cluster_1, levels = rev(unique(cluster_1))),
    cluster_2 = factor(cluster_2, levels = unique(cluster_2))
  ) %>%
  ggplot(aes(cluster_2, cluster_1, fill = probability)) +
  geom_tile(color = 'white') +
  geom_text(aes(label = round(probability, digits = 2)), size = 2.5) +
  scale_x_discrete(name = 'Cluster', position = 'top') +
  scale_y_discrete(name = 'Cluster') +
  scale_fill_gradient(
    name = 'Probability', low = 'white', high = '#c0392b', na.value = '#bdc3c7',
    limits = c(0,1),
    guide = guide_colorbar(
      frame.colour = 'black', ticks.colour = 'black', title.position = 'left',
      title.theme = element_text(hjust = 1, angle = 90),
      barwidth = 0.75, barheight = 10
    )
  ) +
  coord_fixed() +
  theme_bw() +
  theme(
    legend.position = 'right',
    panel.grid.major = element_blank()
  )

ggsave('plots/cluster_stability.png', p, height = 6, width = 7)
#Silhouette plot
library(cluster)

distance_matrix <- dist(Embeddings(seurat[['pca']])[, 1:15])
clusters <- seurat@meta.data$seurat_clusters
silhouette <- silhouette(as.numeric(clusters), dist = distance_matrix)
seurat@meta.data$silhouette_score <- silhouette[,3]

mean_silhouette_score <- mean(seurat@meta.data$silhouette_score)

p <- seurat@meta.data %>%
  mutate(barcode = rownames(.)) %>%
  arrange(seurat_clusters,-silhouette_score) %>%
  mutate(barcode = factor(barcode, levels = barcode)) %>%
  ggplot() +
  geom_col(aes(barcode, silhouette_score, fill = seurat_clusters), show.legend = FALSE) +
  geom_hline(yintercept = mean_silhouette_score, color = 'red', linetype = 'dashed') +
  scale_x_discrete(name = 'Cells') +
  scale_y_continuous(name = 'Silhouette score') +
  scale_fill_manual(values = custom_colors$discrete) +
  theme_bw() +
  theme(
    axis.title.x = element_blank(),
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank()
  )
ggsave('plots/silhouette_plot.png', p, height = 4, width = 8)
#Cluster tree
seurat <- BuildClusterTree(
  seurat,
  dims = 1:15,
  reorder = FALSE,
  reorder.numeric = FALSE
)

tree <- seurat@tools$BuildClusterTree
tree$tip.label <- paste0("Cluster ", tree$tip.label)

p <- ggtree::ggtree(tree, aes(x, y)) +
  scale_y_reverse() +
  ggtree::geom_tree() +
  ggtree::theme_tree() +
  ggtree::geom_tiplab(offset = 1) +
  ggtree::geom_tippoint(color = custom_colors$discrete[1:length(tree$tip.label)], shape = 16, size = 5) +
  coord_cartesian(clip = 'off') +
  theme(plot.margin = unit(c(0,2.5,0,0), 'cm'))

ggsave('plots/cluster_tree.png', p, height = 4, width = 6)
##barplot
temp_labels <- seurat@meta.data %>%
  group_by(sample) %>%
  tally()

p1 <- table_samples_by_clusters %>%
  select(-c('total_cell_count')) %>%
  reshape2::melt(id.vars = 'sample') %>%
  mutate(sample = factor(sample, levels = levels(seurat@meta.data$sample))) %>%
  ggplot(aes(sample, value)) +
  geom_bar(aes(fill = variable), position = 'fill', stat = 'identity') +
  geom_text(
    data = temp_labels,
    aes(x = sample, y = Inf, label = paste0('n = ', format(n, big.mark = ',', trim = TRUE)), vjust = -1),
    color = 'black', size = 2.8
  ) +
  scale_fill_manual(name = 'Cluster', values = custom_colors$discrete) +
  scale_y_continuous(name = 'Percentage [%]', labels = scales::percent_format(), expand = c(0.01,0)) +
  coord_cartesian(clip = 'off') +
  theme_bw() +
  theme(
    legend.position = 'left',
    plot.title = element_text(hjust = 0.5),
    text = element_text(size = 16),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.title.x = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1),
    plot.margin = margin(t = 20, r = 0, b = 0, l = 0, unit = 'pt')
  )

temp_labels <- seurat@meta.data %>%
  group_by(seurat_clusters) %>%
  tally() %>%
  dplyr::rename('cluster' = seurat_clusters)

p2 <- table_clusters_by_samples %>%
  select(-c('total_cell_count')) %>%
  reshape2::melt(id.vars = 'cluster') %>%
  mutate(cluster = factor(cluster, levels = levels(seurat@meta.data$seurat_clusters))) %>%
  ggplot(aes(cluster, value)) +
  geom_bar(aes(fill = variable), position = 'fill', stat = 'identity') +
  geom_text(
    data = temp_labels, aes(x = cluster, y = Inf, label = paste0('n = ', format(n, big.mark = ',', trim = TRUE)), vjust = -1),
    color = 'black', size = 2.8
  ) +
  scale_fill_manual(name = 'Sample', values = custom_colors$discrete) +
  scale_y_continuous(name = 'Percentage [%]', labels = scales::percent_format(), expand = c(0.01,0)) +
  coord_cartesian(clip = 'off') +
  theme_bw() +
  theme(
    legend.position = 'right',
    plot.title = element_text(hjust = 0.5),
    text = element_text(size = 16),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.title = element_blank(),
    plot.margin = margin(t = 20, r = 0, b = 0, l = 10, unit = 'pt')
  )

ggsave(
  'plots/composition_samples_clusters_by_percent.png',
  p1 + p2 +
  plot_layout(ncol = 2, widths = c(
    seurat@meta.data$sample %>% unique() %>% length(),
    seurat@meta.data$seurat_clusters %>% unique() %>% length()
  )),
  width = 18, height = 8
)
temp_labels <- seurat@meta.data %>%
  group_by(sample) %>%
  tally()

p1 <- table_samples_by_cell_cycle %>%
  select(-c('total_cell_count')) %>%
  reshape2::melt(id.vars = 'sample') %>%
  mutate(sample = factor(sample, levels = levels(seurat@meta.data$sample))) %>%
  ggplot(aes(sample, value)) +
  geom_bar(aes(fill = variable), position = 'fill', stat = 'identity') +
  geom_text(
    data = temp_labels,
    aes(x = sample, y = Inf, label = paste0('n = ', format(n, big.mark = ',', trim = TRUE)), vjust = -1),
    color = 'black', size = 2.8
  ) +
  scale_fill_manual(name = 'Cell cycle', values = custom_colors$cell_cycle) +
  scale_y_continuous(name = 'Percentage [%]', labels = scales::percent_format(), expand = c(0.01,0)) +
  coord_cartesian(clip = 'off') +
  theme_bw() +
  theme(
    legend.position = 'none',
    plot.title = element_text(hjust = 0.5),
    text = element_text(size = 16),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.title.x = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1),
    plot.margin = margin(t = 20, r = 0, b = 0, l = 0, unit = 'pt')
  )

temp_labels <- seurat@meta.data %>%
  group_by(seurat_clusters) %>%
  tally() %>%
  dplyr::rename('cluster' = seurat_clusters)

p2 <- table_clusters_by_cell_cycle %>%
  select(-c('total_cell_count')) %>%
  reshape2::melt(id.vars = 'cluster') %>%
  mutate(cluster = factor(cluster, levels = levels(seurat@meta.data$seurat_clusters))) %>%
  ggplot(aes(cluster, value)) +
  geom_bar(aes(fill = variable), position = 'fill', stat = 'identity') +
  geom_text(
    data = temp_labels,
    aes(x = cluster, y = Inf, label = paste0('n = ', format(n, big.mark = ',', trim = TRUE)), vjust = -1),
    color = 'black', size = 2.8
  ) +
  scale_fill_manual(name = 'Cell cycle', values = custom_colors$cell_cycle) +
  scale_y_continuous(name = 'Percentage [%]', labels = scales::percent_format(), expand = c(0.01,0)) +
  coord_cartesian(clip = 'off') +
  theme_bw() +
  theme(
    legend.position = 'right',
    plot.title = element_text(hjust = 0.5),
    text = element_text(size = 16),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.title = element_blank(),
    plot.margin = margin(t = 20, r = 0, b = 0, l = 10, unit = 'pt')
  )

ggsave(
  'plots/composition_samples_clusters_by_cell_cycle_by_percent.png',
  p1 + p2 +
  plot_layout(ncol = 2, widths = c(
    seurat@meta.data$sample %>% unique() %>% length(),
    seurat@meta.data$seurat_clusters %>% unique() %>% length()
  )),
  width = 18, height = 8
)
#SNN graph
library(ggnetwork)

SCT_snn <- seurat@graphs$SCT_snn %>%
  as.matrix() %>%
  ggnetwork() %>%
  left_join(seurat@meta.data %>% mutate(vertex.names = rownames(.)), by = 'vertex.names')

p1 <- ggplot(SCT_snn, aes(x = x, y = y, xend = xend, yend = yend)) +
  geom_edges(color = 'grey50', alpha = 0.05) +
  geom_nodes(aes(color = sample), size = 0.5) +
  scale_color_manual(
    name = 'Sample', values = custom_colors$discrete,
    guide = guide_legend(ncol = 1, override.aes = list(size = 2))
  ) +
  theme_blank() +
  theme(legend.position = 'left') +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

p2 <- ggplot(SCT_snn, aes(x = x, y = y, xend = xend, yend = yend)) +
  geom_edges(color = 'grey50', alpha = 0.05) +
  geom_nodes(aes(color = seurat_clusters), size = 0.5) +
  scale_color_manual(
    name = 'Cluster', values = custom_colors$discrete,
    guide = guide_legend(ncol = 1, override.aes = list(size = 2))
  ) +
  theme_blank() +
  theme(legend.position = 'right') +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

ggsave(
  'plots/snn_graph_by_sample_cluster.png',
  p1 + p2 + plot_layout(ncol = 2),
  height = 5, width = 11
)
#UMAP
plot_umap_by_nCount <- bind_cols(seurat@meta.data, as.data.frame(seurat@reductions$UMAP@cell.embeddings)) %>%
  ggplot(aes(UMAP_1, UMAP_2, color = nCount_RNA)) +
  geom_point(size = 0.2) +
  theme_bw() +
  scale_color_viridis(
    guide = guide_colorbar(frame.colour = 'black', ticks.colour = 'black'),
    labels = scales::comma,
  ) +
  labs(color = 'Number of\ntranscripts') +
  theme(legend.position = 'left') +
  coord_fixed() +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

plot_umap_by_sample <- bind_cols(seurat@meta.data, as.data.frame(seurat@reductions$UMAP@cell.embeddings)) %>%
  ggplot(aes(UMAP_1, UMAP_2, color = sample)) +
  geom_point(size = 0.2) +
  theme_bw() +
  scale_color_manual(values = custom_colors$discrete) +
  labs(color = 'Sample') +
  guides(colour = guide_legend(override.aes = list(size = 2))) +
  theme(legend.position = 'right') +
  coord_fixed() +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

plot_umap_by_cluster <- bind_cols(seurat@meta.data, as.data.frame(seurat@reductions$UMAP@cell.embeddings)) %>%
  ggplot(aes(UMAP_1, UMAP_2, color = seurat_clusters)) +
  geom_point(size = 0.2) +
  theme_bw() +
  scale_color_manual(
    name = 'Cluster', values = custom_colors$discrete,
    guide = guide_legend(ncol = 2, override.aes = list(size = 2))
  ) +
  theme(legend.position = 'left') +
  coord_fixed() +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

plot_umap_by_cell_cycle <- bind_cols(seurat@meta.data, as.data.frame(seurat@reductions$UMAP@cell.embeddings)) %>%
  ggplot(aes(UMAP_1, UMAP_2, color = cell_cycle_seurat)) +
  geom_point(size = 0.2) +
  theme_bw() +
  scale_color_manual(values = custom_colors$cell_cycle) +
  labs(color = 'Cell cycle') +
  guides(colour = guide_legend(override.aes = list(size = 2))) +
  theme(legend.position = 'right') +
  coord_fixed() +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

ggsave(
  'plots/umap.png',
  plot_umap_by_nCount + plot_umap_by_sample +
  plot_umap_by_cluster + plot_umap_by_cell_cycle +
  plot_layout(ncol = 2),
  height = 6,
  width = 8.5
)
#Alluvial plots
## get sample and cluster names
samples <- levels(seurat@meta.data$sample)
clusters <- levels(seurat@meta.data$seurat_clusters)

## create named vector holding the color assignments for both samples and
## clusters
color_assignments <- setNames(
  c(custom_colors$discrete[1:length(samples)], custom_colors$discrete[1:length(clusters)]),
  c(samples,clusters)
)

## prepare data for the plot; factor() calls are necessary for the right order
## of columns (first samples then clusters) and boxes within each column (
## cluster 1, 2, 3, ..., not 1, 10, 11, ...)
data <- seurat@meta.data %>%
  group_by(sample,seurat_clusters) %>%
  tally() %>%
  ungroup() %>%
  gather_set_data(1:2) %>%
  dplyr::mutate(
    x = factor(x, levels = unique(x)),
    y = factor(y, levels = unique(y))
  )

DataFrame(data)
# DataFrame with 114 rows and 6 columns
#       sample seurat_clusters         n        id               x        y
#     <factor>        <factor> <integer> <integer>        <factor> <factor>
# 1          A               0       301         1          sample        A
# 2          A               1       137         2          sample        A
# 3          A               2       121         3          sample        A
# 4          A               3       223         4          sample        A
# 5          A               4        78         5          sample        A
# ...      ...             ...       ...       ...             ...      ...
# 110        E              14        19        53 seurat_clusters       14
# 111        E              15        18        54 seurat_clusters       15
# 112        E              16        10        55 seurat_clusters       16
# 113        E              17         9        56 seurat_clusters       17
# 114        E              18        15        57 seurat_clusters       18

## create sample and cluster labels; hjust defines whether a label will be
## aligned to the right (1) or to the left (0); the nudge_x parameter is used
## to move the label outside of the boxes
data_labels <- tibble(
    group = c(
      rep('sample', length(samples)),
      rep('seurat_clusters', length(clusters))
    )
 ) %>%
  mutate(
    hjust = ifelse(group == 'sample', 1, 0),
    nudge_x = ifelse(group == 'sample', -0.1, 0.1)
  )

DataFrame(data_labels)
# DataFrame with 22 rows and 3 columns
#               group     hjust   nudge_x
#         <character> <numeric> <numeric>
# 1            sample         1      -0.1
# 2            sample         1      -0.1
# 3            sample         1      -0.1
# 4   seurat_clusters         0       0.1
# 5   seurat_clusters         0       0.1
# ...             ...       ...       ...
# 18  seurat_clusters         0       0.1
# 19  seurat_clusters         0       0.1
# 20  seurat_clusters         0       0.1
# 21  seurat_clusters         0       0.1
# 22  seurat_clusters         0       0.1

## create plot
p1 <- ggplot(data, aes(x, id = id, split = y, value = n)) +
  geom_parallel_sets(aes(fill = seurat_clusters), alpha = 0.75, axis.width = 0.15) +
  geom_parallel_sets_axes(aes(fill = y), color = 'black', axis.width = 0.1) +
  geom_text(
    aes(y = n, split = y), stat = 'parallel_sets_axes', fontface = 'bold',
    hjust = data_labels$hjust, nudge_x = data_labels$nudge_x
  ) +
  scale_x_discrete(labels = c('Sample','Cluster')) +
  scale_fill_manual(values = color_assignments) +
  theme_bw() +
  theme(
    legend.position = 'none',
    axis.title = element_blank(),
    axis.text.x = element_text(face = 'bold', colour = 'black', size = 15),
    axis.text.y = element_blank(),
    axis.ticks = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank()
  )

clusters <- levels(seurat@meta.data$seurat_clusters)
cell_types <- sort(unique(seurat@meta.data$cell_type_singler_blueprintencode_main))

color_assignments <- setNames(
  c(custom_colors$discrete[1:length(clusters)], custom_colors$discrete[1:length(cell_types)]),
  c(clusters,cell_types)
)

data <- seurat@meta.data %>%
  dplyr::rename(cell_type = cell_type_singler_blueprintencode_main) %>%
  dplyr::mutate(cell_type = factor(cell_type, levels = cell_types)) %>%
  group_by(seurat_clusters, cell_type) %>%
  tally() %>%
  ungroup() %>%
  gather_set_data(1:2) %>%
  dplyr::mutate(
    x = factor(x, levels = unique(x)),
    y = factor(y, levels = c(clusters,cell_types))
  )

data_labels <- tibble(
    group = c(
      rep('seurat_clusters', length(clusters)),
      rep('cell_type', length(cell_types))
    )
 ) %>%
  mutate(
    hjust = ifelse(group == 'seurat_clusters', 1, 0),
    nudge_x = ifelse(group == 'seurat_clusters', -0.1, 0.1)
  )

p2 <- ggplot(data, aes(x, id = id, split = y, value = n)) +
  geom_parallel_sets(aes(fill = seurat_clusters), alpha = 0.75, axis.width = 0.15) +
  geom_parallel_sets_axes(aes(fill = y), color = 'black', axis.width = 0.1) +
  geom_text(
    aes(y = n, split = y), stat = 'parallel_sets_axes', fontface = 'bold',
    hjust = data_labels$hjust, nudge_x = data_labels$nudge_x
  ) +
  scale_x_discrete(labels = c('Cluster','Cell type')) +
  scale_fill_manual(values = color_assignments) +
  theme_bw() +
  theme(
    legend.position = 'none',
    axis.title = element_blank(),
    axis.text.x = element_text(face = 'bold', colour = 'black', size = 15),
    axis.text.y = element_blank(),
    axis.ticks = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank()
  )

ggsave(
  'plots/samples_clusters_cell_types_alluvial.png',
  p1 + p2 + plot_layout(ncol = 2),
  height = 6, width = 8
)
#UMAP by cell type
UMAP_centers_cell_type <- tibble(
    UMAP_1 = as.data.frame(seurat@reductions$UMAP@cell.embeddings)$UMAP_1,
    UMAP_2 = as.data.frame(seurat@reductions$UMAP@cell.embeddings)$UMAP_2,
    cell_type = seurat@meta.data$cell_type_singler_blueprintencode_main
  ) %>%
  group_by(cell_type) %>%
  summarize(x = median(UMAP_1), y = median(UMAP_2))

p <- bind_cols(seurat@meta.data, as.data.frame(seurat@reductions$UMAP@cell.embeddings)) %>%
  ggplot(aes(UMAP_1, UMAP_2, color = cell_type_singler_blueprintencode_main)) +
  geom_point(size = 0.2) +
  geom_label(
    data = UMAP_centers_cell_type,
    mapping = aes(x, y, label = cell_type),
    size = 3.5,
    fill = 'white',
    color = 'black',
    fontface = 'bold',
    alpha = 0.5,
    label.size = 0,
    show.legend = FALSE
  ) +
  theme_bw() +
  expand_limits(x = c(-22,15)) +
  scale_color_manual(values = custom_colors$discrete) +
  labs(color = 'Cell type') +
  guides(colour = guide_legend(override.aes = list(size = 2))) +
  theme(legend.position = 'right') +
  coord_fixed() +
  annotate(
    geom = 'text', x = Inf, y = -Inf,
    label = paste0('n = ', format(nrow(seurat@meta.data), big.mark = ',', trim = TRUE)),
    vjust = -1.5, hjust = 1.25, color = 'black', size = 2.5
  )

ggsave(
  'plots/umap_by_cell_type_singler_blueprintencode_main.png',
  p,
  height = 4,
  width = 6
)
#Dot plot (multiple genes)
# cells will be grouped by samples that they have been assigned to
group_ids <- unique(seurat@meta.data$cell_type_singler_blueprintencode_main)

# select a set of genes for which we want to show expression
genes_to_show <- seurat@misc$marker_genes$cerebro_seurat$cell_type_singler_blueprintencode_main %>%
  group_by(cell_type_singler_blueprintencode_main) %>%
  arrange(p_val_adj) %>%
  filter(row_number() == 1) %>%
  arrange(cell_type_singler_blueprintencode_main) %>%
  pull(gene)

# for every sample-gene combination, calculate the average expression across
# all cells and then transform the data into a data frame
expression_levels_per_group <- vapply(
    group_ids, FUN.VALUE = numeric(length(genes_to_show)), function(x) {
      cells_in_current_group <- which(seurat@meta.data$cell_type_singler_blueprintencode_main == x)
      Matrix::rowMeans(seurat@assays$SCT@data[genes_to_show,cells_in_current_group])
    }
  ) %>%
  t() %>%
  as.data.frame() %>%
  mutate(cell_type_singler_blueprintencode_main = rownames(.)) %>%
  select(cell_type_singler_blueprintencode_main, everything()) %>%
  pivot_longer(
    cols = c(2:ncol(.)),
    names_to = 'gene'
  ) %>%
  dplyr::rename(expression = value) %>%
  mutate(id_to_merge = paste0(cell_type_singler_blueprintencode_main, '_', gene))

# for every sample-gene combination, calculate the percentage of cells in the
# respective group that has at least 1 transcript (this means we consider it
# as expressing the gene) and then transform the data into a data frame
percentage_of_cells_expressing_gene <- vapply(
    group_ids, FUN.VALUE = numeric(length(genes_to_show)), function(x) {
      cells_in_current_group <- which(seurat@meta.data$cell_type_singler_blueprintencode_main == x)
      Matrix::rowSums(seurat@assays$SCT@data[genes_to_show,cells_in_current_group] != 0)
    }
  ) %>%
  t() %>%
  as.data.frame() %>%
  mutate(cell_type_singler_blueprintencode_main = rownames(.)) %>%
  select(cell_type_singler_blueprintencode_main, everything()) %>%
  pivot_longer(
    cols = c(2:ncol(.)),
    names_to = 'gene'
  ) %>%
  dplyr::rename(cell_count = value) %>%
  left_join(
    .,
    seurat@meta.data %>%
      group_by(cell_type_singler_blueprintencode_main) %>%
      tally(),
    by = 'cell_type_singler_blueprintencode_main') %>%
  mutate(
    id_to_merge = paste0(cell_type_singler_blueprintencode_main, '_', gene),
    percent_cells = cell_count / n
  )

# merge the two data frames created before and plot the data
p <- left_join(
    expression_levels_per_group,
    percentage_of_cells_expressing_gene %>% select(id_to_merge, percent_cells),
    by = 'id_to_merge'
  ) %>%
  mutate(
    cell_type_singler_blueprintencode_main = factor(cell_type_singler_blueprintencode_main, levels = rev(group_ids)),
    gene = factor(gene, levels = genes_to_show)
  ) %>%
  arrange(gene) %>%
  ggplot(aes(gene, cell_type_singler_blueprintencode_main)) +
  geom_point(aes(color = expression, size = percent_cells)) +
  scale_color_distiller(
    palette = 'Reds',
    direction = 1,
    name = 'Log-normalised\nexpression',
    guide = guide_colorbar(frame.colour = "black", ticks.colour = "black")
  ) +
  scale_size(name = 'Percent\nof cells', labels = scales::percent) +
  labs(y = 'Cell type', color = 'Expression') +
  coord_fixed() +
  theme_bw() +
  theme(
    axis.title = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

ggsave('plots/marker_genes_by_cell_type_as_dot_plot.png', p, height = 5, width = 6)
group_ids <- levels(seurat@meta.data$seurat_clusters)

genes_to_show <- seurat@misc$marker_genes$cerebro_seurat$seurat_clusters %>%
  group_by(seurat_clusters) %>%
  arrange(p_val_adj) %>%
  filter(row_number() == 1) %>%
  arrange(seurat_clusters) %>%
  pull(gene)

expression_levels_per_group <- vapply(
    group_ids, FUN.VALUE = numeric(length(genes_to_show)), function(x) {
      cells_in_current_group <- which(seurat@meta.data$seurat_clusters == x)
      Matrix::rowMeans(seurat@assays$SCT@data[genes_to_show,cells_in_current_group])
    }
  ) %>%
  t() %>%
  as.data.frame() %>%
  mutate(cluster = rownames(.)) %>%
  select(cluster, everything()) %>%
  pivot_longer(
    cols = c(2:ncol(.)),
    names_to = 'gene'
  ) %>%
  dplyr::rename(expression = value) %>%
  mutate(id_to_merge = paste0(cluster, '_', gene))

percentage_of_cells_expressing_gene <- vapply(
    group_ids, FUN.VALUE = numeric(length(genes_to_show)), function(x) {
      cells_in_current_group <- which(seurat@meta.data$seurat_cluster == x)
      Matrix::rowSums(seurat@assays$SCT@data[genes_to_show,cells_in_current_group] != 0)
    }
  ) %>%
  t() %>%
  as.data.frame() %>%
  mutate(cluster = rownames(.)) %>%
  select(cluster, everything()) %>%
  pivot_longer(
    cols = c(2:ncol(.)),
    names_to = 'gene'
  ) %>%
  dplyr::rename(cell_count = value) %>%
  left_join(
    .,
    seurat@meta.data %>%
      group_by(seurat_clusters) %>%
      tally() %>%
      dplyr::rename(cluster = seurat_clusters),
    by = 'cluster') %>%
  mutate(
    id_to_merge = paste0(cluster, '_', gene),
    percent_cells = cell_count / n
  )

p <- left_join(
    expression_levels_per_group,
    percentage_of_cells_expressing_gene %>% select(id_to_merge, percent_cells),
    by = 'id_to_merge'
  ) %>%
  mutate(
    cluster = factor(cluster, levels = rev(group_ids)),
    gene = factor(gene, levels = genes_to_show)
  ) %>%
  arrange(gene) %>%
  ggplot(aes(gene, cluster)) +
  geom_point(aes(color = expression, size = percent_cells)) +
  scale_color_distiller(
    palette = 'Reds',
    direction = 1,
    name = 'Log-normalised\nexpression',
    guide = guide_colorbar(frame.colour = "black", ticks.colour = "black")
  ) +
  scale_size(name = 'Percent\nof cells', labels = scales::percent) +
  labs(y = 'Cluster', color = 'Expression') +
  coord_fixed() +
  theme_bw() +
  theme(
    axis.title.x = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

ggsave('plots/marker_genes_by_cluster_as_dot_plot.png', p, height = 7, width = 8)
#Gene set enrichment analysis
# function to read GMT file
read_GMT_file <- function(file) {
  gmt <- readr::read_delim(
    file,
    delim = ';',
    col_names = c('X1'),
    col_types = readr::cols()
  )

  gene_set_genes <- list()
  for ( i in seq_len(nrow(gmt)) )
  {
    temp_genes <- strsplit(gmt$X1[i], split = '\t')[[1]] %>% unlist()
    temp_genes <- temp_genes[3:length(temp_genes)]
    gene_set_genes[[i]] <- temp_genes
  }
  gene_set_loaded <- list(
    genesets = gene_set_genes,
    geneset.names = lapply(strsplit(gmt$X1, split = '\t'), '[', 1) %>% unlist(),
    geneset.description = lapply(
        strsplit(gmt$X1, split = '\t'), '[', 2
      ) %>% unlist()
  )

  return(gene_set_loaded)
}

# load gene sets from GMT file
gene_sets <- read_GMT_file('h.all.v7.2.symbols.gmt')

# set gene set names
names(gene_sets$genesets) <- gene_sets$geneset.names

# get indices of cells which are either HSC or monocytes
cells_to_analyze <- seurat@meta.data %>%
  mutate(row_number = row_number()) %>%
  filter(grepl(cell_type_singler_blueprintencode_main, pattern = 'HSC|Monocytes')) %>%
  arrange(cell_type_singler_blueprintencode_main) %>%
  pull(row_number)

# get list of genes unique genes across all gene sets
genes_to_analyze <- gene_sets$genesets %>% unlist() %>% unique()
# filter gene list for those which are present in the data set
genes_to_analyze <- genes_to_analyze[which(genes_to_analyze %in% rownames(seurat@assays$SCT@counts))]

# get expression matrix and reduce it to cells and genes of interest
expression_matrix <- seurat@assays$SCT@counts[ genes_to_analyze , cells_to_analyze] %>% as.matrix()

# perform GSVA
gsva <- GSVA::gsva(
  expression_matrix,
  gset.idx.list = gene_sets$genesets,
  parallel.sz = 1
)

# load limma library for statistical testing
library(limma)

# generate design matrix
design_matrix <- tibble(
  control = 1,
  test = c(
    rep(0, seurat@meta.data %>% filter(cell_type_singler_blueprintencode_main == 'HSC') %>% nrow()),
    rep(1, seurat@meta.data %>% filter(cell_type_singler_blueprintencode_main == 'Monocytes') %>% nrow())
  )
)

head(design_matrix)
# A tibble: 6 x 2
#   control  test
#     <dbl> <dbl>
# 1       1     0
# 2       1     0
# 3       1     0
# 4       1     0
# 5       1     0
# 6       1     0

# fit linear model, followed by empirical Bayes statistics for differential
# enrichment analysis
fit <- lmFit(gsva, design_matrix)
fit <- eBayes(fit)

# prepare data for plotting
data <- topTable(fit, coef = 'test', number = 50) %>%
  mutate(gene_set = rownames(fit$t)) %>%
  arrange(t) %>%
  mutate(
    gene_set = factor(gene_set, levels = gene_set),
    just = ifelse(t < 0, 0, 1),
    nudge_y = ifelse(t < 0, 1, -1),
    color = ifelse(t < -5 | t > 5, 'black', 'grey')
  )

DataFrame(data)
# DataFrame with 50 rows and 10 columns
#         logFC     AveExpr         t      P.Value    adj.P.Val         B                            gene_set      just   nudge_y       color
#     <numeric>   <numeric> <numeric>    <numeric>    <numeric> <numeric>                            <factor> <numeric> <numeric> <character>
# 1   -0.369443  -0.1569690  -35.1619 1.26066e-186 1.05055e-185   415.980  HALLMARK_TGF_BETA_SIGNALING                0         1       black
# 2   -0.251413  -0.0820012  -27.2261 1.96152e-127 8.91598e-127   279.761  HALLMARK_NOTCH_SIGNALING                   0         1       black
# 3   -0.288169  -0.1202293  -26.1464 1.40712e-119 5.41201e-119   261.689  HALLMARK_ESTROGEN_RESPONSE_EARLY           0         1       black
# 4   -0.135034  -0.1511146  -22.3565  8.50194e-93  2.36165e-92   200.094  HALLMARK_INTERFERON_ALPHA_RESPONSE         0         1       black
# 5   -0.203651  -0.1049498  -22.0728  7.44006e-91  1.95791e-90   195.628  HALLMARK_INTERFERON_GAMMA_RESPONSE         0         1       black
# ...       ...         ...       ...          ...          ...       ...                                 ...       ...       ...         ...
# 46   0.200259  0.25341594   35.9250 2.31901e-192 2.31901e-191   429.182 HALLMARK_WNT_BETA_CATENIN_SIGNALING         1        -1       black
# 47   0.293113 -0.08115610   36.1995 2.00784e-194 2.50980e-193   433.929 HALLMARK_MITOTIC_SPINDLE                    1        -1       black
# 48   0.338449  0.11583774   36.2560 7.56196e-195 1.26033e-193   434.906 HALLMARK_CHOLESTEROL_HOMEOSTASIS            1        -1       black
# 49   0.251678  0.00262136   37.5117 2.86772e-204 7.16930e-203   456.592 HALLMARK_HYPOXIA                            1        -1       black
# 50   0.282907 -0.05021404   48.5971 1.72523e-285 8.62616e-284   643.571 HALLMARK_TNFA_SIGNALING_VIA_NFKB            1        -1       black

# plot t-value
p <- ggplot(data = data, aes(x = gene_set, y = t, fill = t)) +
  geom_col() +
  geom_hline(yintercept = c(-5,5), linetype = 'dashed', color = 'grey80') +
  geom_text(
    aes(
      x = gene_set,
      y = 0,
      label = gene_set,
      hjust = just,
      color = color
    ),
    nudge_y = data$nudge_y, size = 3
  ) +
  scale_x_discrete(name = '', labels = NULL) +
  scale_y_continuous(name = 't-value', limits = c(-55,55)) +
  scale_fill_distiller(palette = 'Spectral', limits = c(-max(data$t), max(data$t))) +
  scale_color_manual(values = c('black' = 'black', 'grey' = 'grey')) +
  coord_flip() +
  theme_bw() +
  theme(
    panel.grid = element_blank(),
    axis.ticks.y =  element_blank(),
    legend.position = 'none'
  )

ggsave('plots/gsva_hallmark_gene_sets_monocytes_vs_hsc.png', height = 7.5, width = 7.5)
##Heatmap
library(pheatmap)
library(argparse)

heatmap <- function (Matrix,labels){
     plot_heatmap <- pheatmap(Matrix,scale='none',cluster_cols = F,cluster_rows = F,cellwidth = 20,cellheight = 20,color = colorRampPalette(colors = c("white","red"))(100),display_numbers=labels)
     return(plot_heatmap)
}

getSig <- function(dc) {
if(dc > pvalue){sc <- ""}
if(dc < pvalue){sc <- "*"}}

parser = ArgumentParser()
parser$add_argument("--LR_score", help="the file of sample.LR.score.xls",required =T)
parser$add_argument("--pvalue", help="the file of sample.LR.score.xls",default = '0.1')
parser$add_argument("--outdir", help="the outdir",default='.')
args <- parser$parse_args()
str(args)

LR_score = args$LR_score
pvalue = args$pvalue
outdir = args$outdir

pvalue <- as.numeric(pvalue)

if(!dir.exists(outdir)){
  dir.create(outdir)
}

setwd(outdir)
file <- read.table(LR_score,sep='\t',header=T,row.names=1,check.names=F)
len <- as.numeric(length(colnames(file)))
Max <- max(as.numeric(sapply(strsplit(colnames(file),'_'),"[",2)[1:len/2]))

for (i in 1:Max){
index_score <- as.array(which(sapply(strsplit(colnames(file),'_'),"[",1) == paste0('cluster',as.character(i))))
index_pvalue <- as.array(which(sapply(strsplit(colnames(file),'_'),"[",2) == paste0('pvalue',as.character(i))))
index <- c(index_score,index_pvalue)
Matrix <- file[,index]
LR <- rownames(as.matrix(sort(apply(Matrix,1,sum),decreasing=T)[1:30]))
Matrix_LR <- Matrix[LR,]
label <- as.matrix(sapply(Matrix_LR[,as.numeric(Max+1):as.numeric(length(colnames(Matrix_LR)))],function(x){ifelse(x>pvalue,sc<-"",sc<-"*")}),ncol=Max)
pdf(paste0('cluster',i,'_others_LR.pdf'),width=6,height=14)
heatmap(Matrix_LR[,1:Max],label)
dev.off()
png(paste0('cluster',i,'_others_LR.png'),width=6*200,height=14*200,res=200,type = 'cario-png')
dev.off()
}
LS0tCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAotLS0KLS0tCnRpdGxlOiAiSW1wb3JhdG50IGFuYWx5c2lzIHNjcmlwdHMgdXNpbmcgc2V1cmF0IG9iamVjdCIKb3V0cHV0OiBodG1sX25vdGVib29rCkF1dGhvcjpVcGFzbmEgU3JpdmFzdGF2YQoKYGBge3J9CnNjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChzZXVyYXQpCnJlZHVjZWREaW0oc2NlLCAnUENBX3N1YicpIDwtIHJlZHVjZWREaW0oc2NlLCAnUENBJylbLDE6MTUsIGRyb3AgPSBGQUxTRV0KCmFzc19wcm9iIDwtIGJvb3RzdHJhcENsdXN0ZXIoc2NlLCBGVU4gPSBmdW5jdGlvbih4KSB7CiAgICBnIDwtIGJ1aWxkU05OR3JhcGgoeCwgdXNlLmRpbXJlZCA9ICdQQ0Ffc3ViJykKICAgIGlncmFwaDo6Y2x1c3Rlcl93YWxrdHJhcChnKSRtZW1iZXJzaGlwCiAgfSwKICBjbHVzdGVycyA9IHNjZSRzZXVyYXRfY2x1c3RlcnMKKQoKcCA8LSBhc3NfcHJvYiAlPiUKICBhc190aWJibGUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4odmFyID0gJ2NsdXN0ZXJfMScpICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSAyOm5jb2woLiksCiAgICBuYW1lc190byA9ICdjbHVzdGVyXzInLAogICAgdmFsdWVzX3RvID0gJ3Byb2JhYmlsaXR5JwogICkgJT4lCiAgbXV0YXRlKAogICAgY2x1c3Rlcl8xID0gYXMuY2hhcmFjdGVyKGFzLm51bWVyaWMoY2x1c3Rlcl8xKSAtIDEpLAogICAgY2x1c3Rlcl8xID0gZmFjdG9yKGNsdXN0ZXJfMSwgbGV2ZWxzID0gcmV2KHVuaXF1ZShjbHVzdGVyXzEpKSksCiAgICBjbHVzdGVyXzIgPSBmYWN0b3IoY2x1c3Rlcl8yLCBsZXZlbHMgPSB1bmlxdWUoY2x1c3Rlcl8yKSkKICApICU+JQogIGdncGxvdChhZXMoY2x1c3Rlcl8yLCBjbHVzdGVyXzEsIGZpbGwgPSBwcm9iYWJpbGl0eSkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAnd2hpdGUnKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKHByb2JhYmlsaXR5LCBkaWdpdHMgPSAyKSksIHNpemUgPSAyLjUpICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAnQ2x1c3RlcicsIHBvc2l0aW9uID0gJ3RvcCcpICsKICBzY2FsZV95X2Rpc2NyZXRlKG5hbWUgPSAnQ2x1c3RlcicpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KAogICAgbmFtZSA9ICdQcm9iYWJpbGl0eScsIGxvdyA9ICd3aGl0ZScsIGhpZ2ggPSAnI2MwMzkyYicsIG5hLnZhbHVlID0gJyNiZGMzYzcnLAogICAgbGltaXRzID0gYygwLDEpLAogICAgZ3VpZGUgPSBndWlkZV9jb2xvcmJhcigKICAgICAgZnJhbWUuY29sb3VyID0gJ2JsYWNrJywgdGlja3MuY29sb3VyID0gJ2JsYWNrJywgdGl0bGUucG9zaXRpb24gPSAnbGVmdCcsCiAgICAgIHRpdGxlLnRoZW1lID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMSwgYW5nbGUgPSA5MCksCiAgICAgIGJhcndpZHRoID0gMC43NSwgYmFyaGVpZ2h0ID0gMTAKICAgICkKICApICsKICBjb29yZF9maXhlZCgpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcsCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpCiAgKQoKZ2dzYXZlKCdwbG90cy9jbHVzdGVyX3N0YWJpbGl0eS5wbmcnLCBwLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDcpCmBgYAoKYGBge3J9CiNTaWxob3VldHRlIHBsb3QKbGlicmFyeShjbHVzdGVyKQoKZGlzdGFuY2VfbWF0cml4IDwtIGRpc3QoRW1iZWRkaW5ncyhzZXVyYXRbWydwY2EnXV0pWywgMToxNV0pCmNsdXN0ZXJzIDwtIHNldXJhdEBtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzCnNpbGhvdWV0dGUgPC0gc2lsaG91ZXR0ZShhcy5udW1lcmljKGNsdXN0ZXJzKSwgZGlzdCA9IGRpc3RhbmNlX21hdHJpeCkKc2V1cmF0QG1ldGEuZGF0YSRzaWxob3VldHRlX3Njb3JlIDwtIHNpbGhvdWV0dGVbLDNdCgptZWFuX3NpbGhvdWV0dGVfc2NvcmUgPC0gbWVhbihzZXVyYXRAbWV0YS5kYXRhJHNpbGhvdWV0dGVfc2NvcmUpCgpwIDwtIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgbXV0YXRlKGJhcmNvZGUgPSByb3duYW1lcyguKSkgJT4lCiAgYXJyYW5nZShzZXVyYXRfY2x1c3RlcnMsLXNpbGhvdWV0dGVfc2NvcmUpICU+JQogIG11dGF0ZShiYXJjb2RlID0gZmFjdG9yKGJhcmNvZGUsIGxldmVscyA9IGJhcmNvZGUpKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2woYWVzKGJhcmNvZGUsIHNpbGhvdWV0dGVfc2NvcmUsIGZpbGwgPSBzZXVyYXRfY2x1c3RlcnMpLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbl9zaWxob3VldHRlX3Njb3JlLCBjb2xvciA9ICdyZWQnLCBsaW5ldHlwZSA9ICdkYXNoZWQnKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gJ0NlbGxzJykgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gJ1NpbGhvdWV0dGUgc2NvcmUnKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9ycyRkaXNjcmV0ZSkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKQogICkKZ2dzYXZlKCdwbG90cy9zaWxob3VldHRlX3Bsb3QucG5nJywgcCwgaGVpZ2h0ID0gNCwgd2lkdGggPSA4KQpgYGAKCmBgYHtyfQojQ2x1c3RlciB0cmVlCnNldXJhdCA8LSBCdWlsZENsdXN0ZXJUcmVlKAogIHNldXJhdCwKICBkaW1zID0gMToxNSwKICByZW9yZGVyID0gRkFMU0UsCiAgcmVvcmRlci5udW1lcmljID0gRkFMU0UKKQoKdHJlZSA8LSBzZXVyYXRAdG9vbHMkQnVpbGRDbHVzdGVyVHJlZQp0cmVlJHRpcC5sYWJlbCA8LSBwYXN0ZTAoIkNsdXN0ZXIgIiwgdHJlZSR0aXAubGFiZWwpCgpwIDwtIGdndHJlZTo6Z2d0cmVlKHRyZWUsIGFlcyh4LCB5KSkgKwogIHNjYWxlX3lfcmV2ZXJzZSgpICsKICBnZ3RyZWU6Omdlb21fdHJlZSgpICsKICBnZ3RyZWU6OnRoZW1lX3RyZWUoKSArCiAgZ2d0cmVlOjpnZW9tX3RpcGxhYihvZmZzZXQgPSAxKSArCiAgZ2d0cmVlOjpnZW9tX3RpcHBvaW50KGNvbG9yID0gY3VzdG9tX2NvbG9ycyRkaXNjcmV0ZVsxOmxlbmd0aCh0cmVlJHRpcC5sYWJlbCldLCBzaGFwZSA9IDE2LCBzaXplID0gNSkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gJ29mZicpICsKICB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLDIuNSwwLDApLCAnY20nKSkKCmdnc2F2ZSgncGxvdHMvY2x1c3Rlcl90cmVlLnBuZycsIHAsIGhlaWdodCA9IDQsIHdpZHRoID0gNikKYGBgCgpgYGB7cn0KIyNiYXJwbG90CnRlbXBfbGFiZWxzIDwtIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkoc2FtcGxlKSAlPiUKICB0YWxseSgpCgpwMSA8LSB0YWJsZV9zYW1wbGVzX2J5X2NsdXN0ZXJzICU+JQogIHNlbGVjdCgtYygndG90YWxfY2VsbF9jb3VudCcpKSAlPiUKICByZXNoYXBlMjo6bWVsdChpZC52YXJzID0gJ3NhbXBsZScpICU+JQogIG11dGF0ZShzYW1wbGUgPSBmYWN0b3Ioc2FtcGxlLCBsZXZlbHMgPSBsZXZlbHMoc2V1cmF0QG1ldGEuZGF0YSRzYW1wbGUpKSkgJT4lCiAgZ2dwbG90KGFlcyhzYW1wbGUsIHZhbHVlKSkgKwogIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLCBwb3NpdGlvbiA9ICdmaWxsJywgc3RhdCA9ICdpZGVudGl0eScpICsKICBnZW9tX3RleHQoCiAgICBkYXRhID0gdGVtcF9sYWJlbHMsCiAgICBhZXMoeCA9IHNhbXBsZSwgeSA9IEluZiwgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobiwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksIHZqdXN0ID0gLTEpLAogICAgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi44CiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICdDbHVzdGVyJywgdmFsdWVzID0gY3VzdG9tX2NvbG9ycyRkaXNjcmV0ZSkgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gJ1BlcmNlbnRhZ2UgWyVdJywgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBleHBhbmQgPSBjKDAuMDEsMCkpICsKICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICdvZmYnKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAnbGVmdCcsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCB2anVzdCA9IDEpLAogICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4odCA9IDIwLCByID0gMCwgYiA9IDAsIGwgPSAwLCB1bml0ID0gJ3B0JykKICApCgp0ZW1wX2xhYmVscyA8LSBzZXVyYXRAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycykgJT4lCiAgdGFsbHkoKSAlPiUKICBkcGx5cjo6cmVuYW1lKCdjbHVzdGVyJyA9IHNldXJhdF9jbHVzdGVycykKCnAyIDwtIHRhYmxlX2NsdXN0ZXJzX2J5X3NhbXBsZXMgJT4lCiAgc2VsZWN0KC1jKCd0b3RhbF9jZWxsX2NvdW50JykpICU+JQogIHJlc2hhcGUyOjptZWx0KGlkLnZhcnMgPSAnY2x1c3RlcicpICU+JQogIG11dGF0ZShjbHVzdGVyID0gZmFjdG9yKGNsdXN0ZXIsIGxldmVscyA9IGxldmVscyhzZXVyYXRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycykpKSAlPiUKICBnZ3Bsb3QoYWVzKGNsdXN0ZXIsIHZhbHVlKSkgKwogIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLCBwb3NpdGlvbiA9ICdmaWxsJywgc3RhdCA9ICdpZGVudGl0eScpICsKICBnZW9tX3RleHQoCiAgICBkYXRhID0gdGVtcF9sYWJlbHMsIGFlcyh4ID0gY2x1c3RlciwgeSA9IEluZiwgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobiwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksIHZqdXN0ID0gLTEpLAogICAgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi44CiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICdTYW1wbGUnLCB2YWx1ZXMgPSBjdXN0b21fY29sb3JzJGRpc2NyZXRlKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAnUGVyY2VudGFnZSBbJV0nLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGV4cGFuZCA9IGMoMC4wMSwwKSkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gJ29mZicpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAyMCwgciA9IDAsIGIgPSAwLCBsID0gMTAsIHVuaXQgPSAncHQnKQogICkKCmdnc2F2ZSgKICAncGxvdHMvY29tcG9zaXRpb25fc2FtcGxlc19jbHVzdGVyc19ieV9wZXJjZW50LnBuZycsCiAgcDEgKyBwMiArCiAgcGxvdF9sYXlvdXQobmNvbCA9IDIsIHdpZHRocyA9IGMoCiAgICBzZXVyYXRAbWV0YS5kYXRhJHNhbXBsZSAlPiUgdW5pcXVlKCkgJT4lIGxlbmd0aCgpLAogICAgc2V1cmF0QG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgJT4lIHVuaXF1ZSgpICU+JSBsZW5ndGgoKQogICkpLAogIHdpZHRoID0gMTgsIGhlaWdodCA9IDgKKQpgYGAKCmBgYHtyfQp0ZW1wX2xhYmVscyA8LSBzZXVyYXRAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KHNhbXBsZSkgJT4lCiAgdGFsbHkoKQoKcDEgPC0gdGFibGVfc2FtcGxlc19ieV9jZWxsX2N5Y2xlICU+JQogIHNlbGVjdCgtYygndG90YWxfY2VsbF9jb3VudCcpKSAlPiUKICByZXNoYXBlMjo6bWVsdChpZC52YXJzID0gJ3NhbXBsZScpICU+JQogIG11dGF0ZShzYW1wbGUgPSBmYWN0b3Ioc2FtcGxlLCBsZXZlbHMgPSBsZXZlbHMoc2V1cmF0QG1ldGEuZGF0YSRzYW1wbGUpKSkgJT4lCiAgZ2dwbG90KGFlcyhzYW1wbGUsIHZhbHVlKSkgKwogIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLCBwb3NpdGlvbiA9ICdmaWxsJywgc3RhdCA9ICdpZGVudGl0eScpICsKICBnZW9tX3RleHQoCiAgICBkYXRhID0gdGVtcF9sYWJlbHMsCiAgICBhZXMoeCA9IHNhbXBsZSwgeSA9IEluZiwgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobiwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksIHZqdXN0ID0gLTEpLAogICAgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi44CiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICdDZWxsIGN5Y2xlJywgdmFsdWVzID0gY3VzdG9tX2NvbG9ycyRjZWxsX2N5Y2xlKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAnUGVyY2VudGFnZSBbJV0nLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGV4cGFuZCA9IGMoMC4wMSwwKSkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gJ29mZicpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJywKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHZqdXN0ID0gMSksCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbih0ID0gMjAsIHIgPSAwLCBiID0gMCwgbCA9IDAsIHVuaXQgPSAncHQnKQogICkKCnRlbXBfbGFiZWxzIDwtIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICB0YWxseSgpICU+JQogIGRwbHlyOjpyZW5hbWUoJ2NsdXN0ZXInID0gc2V1cmF0X2NsdXN0ZXJzKQoKcDIgPC0gdGFibGVfY2x1c3RlcnNfYnlfY2VsbF9jeWNsZSAlPiUKICBzZWxlY3QoLWMoJ3RvdGFsX2NlbGxfY291bnQnKSkgJT4lCiAgcmVzaGFwZTI6Om1lbHQoaWQudmFycyA9ICdjbHVzdGVyJykgJT4lCiAgbXV0YXRlKGNsdXN0ZXIgPSBmYWN0b3IoY2x1c3RlciwgbGV2ZWxzID0gbGV2ZWxzKHNldXJhdEBtZXRhLmRhdGEkc2V1cmF0X2NsdXN0ZXJzKSkpICU+JQogIGdncGxvdChhZXMoY2x1c3RlciwgdmFsdWUpKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSB2YXJpYWJsZSksIHBvc2l0aW9uID0gJ2ZpbGwnLCBzdGF0ID0gJ2lkZW50aXR5JykgKwogIGdlb21fdGV4dCgKICAgIGRhdGEgPSB0ZW1wX2xhYmVscywKICAgIGFlcyh4ID0gY2x1c3RlciwgeSA9IEluZiwgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobiwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksIHZqdXN0ID0gLTEpLAogICAgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi44CiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICdDZWxsIGN5Y2xlJywgdmFsdWVzID0gY3VzdG9tX2NvbG9ycyRjZWxsX2N5Y2xlKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAnUGVyY2VudGFnZSBbJV0nLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGV4cGFuZCA9IGMoMC4wMSwwKSkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gJ29mZicpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAyMCwgciA9IDAsIGIgPSAwLCBsID0gMTAsIHVuaXQgPSAncHQnKQogICkKCmdnc2F2ZSgKICAncGxvdHMvY29tcG9zaXRpb25fc2FtcGxlc19jbHVzdGVyc19ieV9jZWxsX2N5Y2xlX2J5X3BlcmNlbnQucG5nJywKICBwMSArIHAyICsKICBwbG90X2xheW91dChuY29sID0gMiwgd2lkdGhzID0gYygKICAgIHNldXJhdEBtZXRhLmRhdGEkc2FtcGxlICU+JSB1bmlxdWUoKSAlPiUgbGVuZ3RoKCksCiAgICBzZXVyYXRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycyAlPiUgdW5pcXVlKCkgJT4lIGxlbmd0aCgpCiAgKSksCiAgd2lkdGggPSAxOCwgaGVpZ2h0ID0gOAopCmBgYAoKYGBge3J9CiNTTk4gZ3JhcGgKbGlicmFyeShnZ25ldHdvcmspCgpTQ1Rfc25uIDwtIHNldXJhdEBncmFwaHMkU0NUX3NubiAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICBnZ25ldHdvcmsoKSAlPiUKICBsZWZ0X2pvaW4oc2V1cmF0QG1ldGEuZGF0YSAlPiUgbXV0YXRlKHZlcnRleC5uYW1lcyA9IHJvd25hbWVzKC4pKSwgYnkgPSAndmVydGV4Lm5hbWVzJykKCnAxIDwtIGdncGxvdChTQ1Rfc25uLCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geGVuZCwgeWVuZCA9IHllbmQpKSArCiAgZ2VvbV9lZGdlcyhjb2xvciA9ICdncmV5NTAnLCBhbHBoYSA9IDAuMDUpICsKICBnZW9tX25vZGVzKGFlcyhjb2xvciA9IHNhbXBsZSksIHNpemUgPSAwLjUpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICBuYW1lID0gJ1NhbXBsZScsIHZhbHVlcyA9IGN1c3RvbV9jb2xvcnMkZGlzY3JldGUsCiAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZChuY29sID0gMSwgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMikpCiAgKSArCiAgdGhlbWVfYmxhbmsoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2xlZnQnKSArCiAgYW5ub3RhdGUoCiAgICBnZW9tID0gJ3RleHQnLCB4ID0gSW5mLCB5ID0gLUluZiwKICAgIGxhYmVsID0gcGFzdGUwKCduID0gJywgZm9ybWF0KG5yb3coc2V1cmF0QG1ldGEuZGF0YSksIGJpZy5tYXJrID0gJywnLCB0cmltID0gVFJVRSkpLAogICAgdmp1c3QgPSAtMS41LCBoanVzdCA9IDEuMjUsIGNvbG9yID0gJ2JsYWNrJywgc2l6ZSA9IDIuNQogICkKCnAyIDwtIGdncGxvdChTQ1Rfc25uLCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geGVuZCwgeWVuZCA9IHllbmQpKSArCiAgZ2VvbV9lZGdlcyhjb2xvciA9ICdncmV5NTAnLCBhbHBoYSA9IDAuMDUpICsKICBnZW9tX25vZGVzKGFlcyhjb2xvciA9IHNldXJhdF9jbHVzdGVycyksIHNpemUgPSAwLjUpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICBuYW1lID0gJ0NsdXN0ZXInLCB2YWx1ZXMgPSBjdXN0b21fY29sb3JzJGRpc2NyZXRlLAogICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQobmNvbCA9IDEsIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKQogICkgKwogIHRoZW1lX2JsYW5rKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcpICsKICBhbm5vdGF0ZSgKICAgIGdlb20gPSAndGV4dCcsIHggPSBJbmYsIHkgPSAtSW5mLAogICAgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobnJvdyhzZXVyYXRAbWV0YS5kYXRhKSwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksCiAgICB2anVzdCA9IC0xLjUsIGhqdXN0ID0gMS4yNSwgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi41CiAgKQoKZ2dzYXZlKAogICdwbG90cy9zbm5fZ3JhcGhfYnlfc2FtcGxlX2NsdXN0ZXIucG5nJywKICBwMSArIHAyICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpLAogIGhlaWdodCA9IDUsIHdpZHRoID0gMTEKKQoKYGBgCgpgYGB7cn0KI1VNQVAKcGxvdF91bWFwX2J5X25Db3VudCA8LSBiaW5kX2NvbHMoc2V1cmF0QG1ldGEuZGF0YSwgYXMuZGF0YS5mcmFtZShzZXVyYXRAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5ncykpICU+JQogIGdncGxvdChhZXMoVU1BUF8xLCBVTUFQXzIsIGNvbG9yID0gbkNvdW50X1JOQSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjIpICsKICB0aGVtZV9idygpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzKAogICAgZ3VpZGUgPSBndWlkZV9jb2xvcmJhcihmcmFtZS5jb2xvdXIgPSAnYmxhY2snLCB0aWNrcy5jb2xvdXIgPSAnYmxhY2snKSwKICAgIGxhYmVscyA9IHNjYWxlczo6Y29tbWEsCiAgKSArCiAgbGFicyhjb2xvciA9ICdOdW1iZXIgb2ZcbnRyYW5zY3JpcHRzJykgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdsZWZ0JykgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGFubm90YXRlKAogICAgZ2VvbSA9ICd0ZXh0JywgeCA9IEluZiwgeSA9IC1JbmYsCiAgICBsYWJlbCA9IHBhc3RlMCgnbiA9ICcsIGZvcm1hdChucm93KHNldXJhdEBtZXRhLmRhdGEpLCBiaWcubWFyayA9ICcsJywgdHJpbSA9IFRSVUUpKSwKICAgIHZqdXN0ID0gLTEuNSwgaGp1c3QgPSAxLjI1LCBjb2xvciA9ICdibGFjaycsIHNpemUgPSAyLjUKICApCgpwbG90X3VtYXBfYnlfc2FtcGxlIDwtIGJpbmRfY29scyhzZXVyYXRAbWV0YS5kYXRhLCBhcy5kYXRhLmZyYW1lKHNldXJhdEByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzKSkgJT4lCiAgZ2dwbG90KGFlcyhVTUFQXzEsIFVNQVBfMiwgY29sb3IgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4yKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9jb2xvcnMkZGlzY3JldGUpICsKICBsYWJzKGNvbG9yID0gJ1NhbXBsZScpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcpICsKICBjb29yZF9maXhlZCgpICsKICBhbm5vdGF0ZSgKICAgIGdlb20gPSAndGV4dCcsIHggPSBJbmYsIHkgPSAtSW5mLAogICAgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobnJvdyhzZXVyYXRAbWV0YS5kYXRhKSwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksCiAgICB2anVzdCA9IC0xLjUsIGhqdXN0ID0gMS4yNSwgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi41CiAgKQoKcGxvdF91bWFwX2J5X2NsdXN0ZXIgPC0gYmluZF9jb2xzKHNldXJhdEBtZXRhLmRhdGEsIGFzLmRhdGEuZnJhbWUoc2V1cmF0QHJlZHVjdGlvbnMkVU1BUEBjZWxsLmVtYmVkZGluZ3MpKSAlPiUKICBnZ3Bsb3QoYWVzKFVNQVBfMSwgVU1BUF8yLCBjb2xvciA9IHNldXJhdF9jbHVzdGVycykpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjIpICsKICB0aGVtZV9idygpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICBuYW1lID0gJ0NsdXN0ZXInLCB2YWx1ZXMgPSBjdXN0b21fY29sb3JzJGRpc2NyZXRlLAogICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQobmNvbCA9IDIsIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKQogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdsZWZ0JykgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGFubm90YXRlKAogICAgZ2VvbSA9ICd0ZXh0JywgeCA9IEluZiwgeSA9IC1JbmYsCiAgICBsYWJlbCA9IHBhc3RlMCgnbiA9ICcsIGZvcm1hdChucm93KHNldXJhdEBtZXRhLmRhdGEpLCBiaWcubWFyayA9ICcsJywgdHJpbSA9IFRSVUUpKSwKICAgIHZqdXN0ID0gLTEuNSwgaGp1c3QgPSAxLjI1LCBjb2xvciA9ICdibGFjaycsIHNpemUgPSAyLjUKICApCgpwbG90X3VtYXBfYnlfY2VsbF9jeWNsZSA8LSBiaW5kX2NvbHMoc2V1cmF0QG1ldGEuZGF0YSwgYXMuZGF0YS5mcmFtZShzZXVyYXRAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5ncykpICU+JQogIGdncGxvdChhZXMoVU1BUF8xLCBVTUFQXzIsIGNvbG9yID0gY2VsbF9jeWNsZV9zZXVyYXQpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4yKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9jb2xvcnMkY2VsbF9jeWNsZSkgKwogIGxhYnMoY29sb3IgPSAnQ2VsbCBjeWNsZScpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIpKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcpICsKICBjb29yZF9maXhlZCgpICsKICBhbm5vdGF0ZSgKICAgIGdlb20gPSAndGV4dCcsIHggPSBJbmYsIHkgPSAtSW5mLAogICAgbGFiZWwgPSBwYXN0ZTAoJ24gPSAnLCBmb3JtYXQobnJvdyhzZXVyYXRAbWV0YS5kYXRhKSwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUUlVFKSksCiAgICB2anVzdCA9IC0xLjUsIGhqdXN0ID0gMS4yNSwgY29sb3IgPSAnYmxhY2snLCBzaXplID0gMi41CiAgKQoKZ2dzYXZlKAogICdwbG90cy91bWFwLnBuZycsCiAgcGxvdF91bWFwX2J5X25Db3VudCArIHBsb3RfdW1hcF9ieV9zYW1wbGUgKwogIHBsb3RfdW1hcF9ieV9jbHVzdGVyICsgcGxvdF91bWFwX2J5X2NlbGxfY3ljbGUgKwogIHBsb3RfbGF5b3V0KG5jb2wgPSAyKSwKICBoZWlnaHQgPSA2LAogIHdpZHRoID0gOC41CikKCmBgYAoKYGBge3J9CiNBbGx1dmlhbCBwbG90cwojIyBnZXQgc2FtcGxlIGFuZCBjbHVzdGVyIG5hbWVzCnNhbXBsZXMgPC0gbGV2ZWxzKHNldXJhdEBtZXRhLmRhdGEkc2FtcGxlKQpjbHVzdGVycyA8LSBsZXZlbHMoc2V1cmF0QG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMpCgojIyBjcmVhdGUgbmFtZWQgdmVjdG9yIGhvbGRpbmcgdGhlIGNvbG9yIGFzc2lnbm1lbnRzIGZvciBib3RoIHNhbXBsZXMgYW5kCiMjIGNsdXN0ZXJzCmNvbG9yX2Fzc2lnbm1lbnRzIDwtIHNldE5hbWVzKAogIGMoY3VzdG9tX2NvbG9ycyRkaXNjcmV0ZVsxOmxlbmd0aChzYW1wbGVzKV0sIGN1c3RvbV9jb2xvcnMkZGlzY3JldGVbMTpsZW5ndGgoY2x1c3RlcnMpXSksCiAgYyhzYW1wbGVzLGNsdXN0ZXJzKQopCgojIyBwcmVwYXJlIGRhdGEgZm9yIHRoZSBwbG90OyBmYWN0b3IoKSBjYWxscyBhcmUgbmVjZXNzYXJ5IGZvciB0aGUgcmlnaHQgb3JkZXIKIyMgb2YgY29sdW1ucyAoZmlyc3Qgc2FtcGxlcyB0aGVuIGNsdXN0ZXJzKSBhbmQgYm94ZXMgd2l0aGluIGVhY2ggY29sdW1uICgKIyMgY2x1c3RlciAxLCAyLCAzLCAuLi4sIG5vdCAxLCAxMCwgMTEsIC4uLikKZGF0YSA8LSBzZXVyYXRAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KHNhbXBsZSxzZXVyYXRfY2x1c3RlcnMpICU+JQogIHRhbGx5KCkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdhdGhlcl9zZXRfZGF0YSgxOjIpICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICB4ID0gZmFjdG9yKHgsIGxldmVscyA9IHVuaXF1ZSh4KSksCiAgICB5ID0gZmFjdG9yKHksIGxldmVscyA9IHVuaXF1ZSh5KSkKICApCgpEYXRhRnJhbWUoZGF0YSkKIyBEYXRhRnJhbWUgd2l0aCAxMTQgcm93cyBhbmQgNiBjb2x1bW5zCiMgICAgICAgc2FtcGxlIHNldXJhdF9jbHVzdGVycyAgICAgICAgIG4gICAgICAgIGlkICAgICAgICAgICAgICAgeCAgICAgICAgeQojICAgICA8ZmFjdG9yPiAgICAgICAgPGZhY3Rvcj4gPGludGVnZXI+IDxpbnRlZ2VyPiAgICAgICAgPGZhY3Rvcj4gPGZhY3Rvcj4KIyAxICAgICAgICAgIEEgICAgICAgICAgICAgICAwICAgICAgIDMwMSAgICAgICAgIDEgICAgICAgICAgc2FtcGxlICAgICAgICBBCiMgMiAgICAgICAgICBBICAgICAgICAgICAgICAgMSAgICAgICAxMzcgICAgICAgICAyICAgICAgICAgIHNhbXBsZSAgICAgICAgQQojIDMgICAgICAgICAgQSAgICAgICAgICAgICAgIDIgICAgICAgMTIxICAgICAgICAgMyAgICAgICAgICBzYW1wbGUgICAgICAgIEEKIyA0ICAgICAgICAgIEEgICAgICAgICAgICAgICAzICAgICAgIDIyMyAgICAgICAgIDQgICAgICAgICAgc2FtcGxlICAgICAgICBBCiMgNSAgICAgICAgICBBICAgICAgICAgICAgICAgNCAgICAgICAgNzggICAgICAgICA1ICAgICAgICAgIHNhbXBsZSAgICAgICAgQQojIC4uLiAgICAgIC4uLiAgICAgICAgICAgICAuLi4gICAgICAgLi4uICAgICAgIC4uLiAgICAgICAgICAgICAuLi4gICAgICAuLi4KIyAxMTAgICAgICAgIEUgICAgICAgICAgICAgIDE0ICAgICAgICAxOSAgICAgICAgNTMgc2V1cmF0X2NsdXN0ZXJzICAgICAgIDE0CiMgMTExICAgICAgICBFICAgICAgICAgICAgICAxNSAgICAgICAgMTggICAgICAgIDU0IHNldXJhdF9jbHVzdGVycyAgICAgICAxNQojIDExMiAgICAgICAgRSAgICAgICAgICAgICAgMTYgICAgICAgIDEwICAgICAgICA1NSBzZXVyYXRfY2x1c3RlcnMgICAgICAgMTYKIyAxMTMgICAgICAgIEUgICAgICAgICAgICAgIDE3ICAgICAgICAgOSAgICAgICAgNTYgc2V1cmF0X2NsdXN0ZXJzICAgICAgIDE3CiMgMTE0ICAgICAgICBFICAgICAgICAgICAgICAxOCAgICAgICAgMTUgICAgICAgIDU3IHNldXJhdF9jbHVzdGVycyAgICAgICAxOAoKIyMgY3JlYXRlIHNhbXBsZSBhbmQgY2x1c3RlciBsYWJlbHM7IGhqdXN0IGRlZmluZXMgd2hldGhlciBhIGxhYmVsIHdpbGwgYmUKIyMgYWxpZ25lZCB0byB0aGUgcmlnaHQgKDEpIG9yIHRvIHRoZSBsZWZ0ICgwKTsgdGhlIG51ZGdlX3ggcGFyYW1ldGVyIGlzIHVzZWQKIyMgdG8gbW92ZSB0aGUgbGFiZWwgb3V0c2lkZSBvZiB0aGUgYm94ZXMKZGF0YV9sYWJlbHMgPC0gdGliYmxlKAogICAgZ3JvdXAgPSBjKAogICAgICByZXAoJ3NhbXBsZScsIGxlbmd0aChzYW1wbGVzKSksCiAgICAgIHJlcCgnc2V1cmF0X2NsdXN0ZXJzJywgbGVuZ3RoKGNsdXN0ZXJzKSkKICAgICkKICkgJT4lCiAgbXV0YXRlKAogICAgaGp1c3QgPSBpZmVsc2UoZ3JvdXAgPT0gJ3NhbXBsZScsIDEsIDApLAogICAgbnVkZ2VfeCA9IGlmZWxzZShncm91cCA9PSAnc2FtcGxlJywgLTAuMSwgMC4xKQogICkKCkRhdGFGcmFtZShkYXRhX2xhYmVscykKIyBEYXRhRnJhbWUgd2l0aCAyMiByb3dzIGFuZCAzIGNvbHVtbnMKIyAgICAgICAgICAgICAgIGdyb3VwICAgICBoanVzdCAgIG51ZGdlX3gKIyAgICAgICAgIDxjaGFyYWN0ZXI+IDxudW1lcmljPiA8bnVtZXJpYz4KIyAxICAgICAgICAgICAgc2FtcGxlICAgICAgICAgMSAgICAgIC0wLjEKIyAyICAgICAgICAgICAgc2FtcGxlICAgICAgICAgMSAgICAgIC0wLjEKIyAzICAgICAgICAgICAgc2FtcGxlICAgICAgICAgMSAgICAgIC0wLjEKIyA0ICAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKIyA1ICAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKIyAuLi4gICAgICAgICAgICAgLi4uICAgICAgIC4uLiAgICAgICAuLi4KIyAxOCAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKIyAxOSAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKIyAyMCAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKIyAyMSAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKIyAyMiAgc2V1cmF0X2NsdXN0ZXJzICAgICAgICAgMCAgICAgICAwLjEKCiMjIGNyZWF0ZSBwbG90CnAxIDwtIGdncGxvdChkYXRhLCBhZXMoeCwgaWQgPSBpZCwgc3BsaXQgPSB5LCB2YWx1ZSA9IG4pKSArCiAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gc2V1cmF0X2NsdXN0ZXJzKSwgYWxwaGEgPSAwLjc1LCBheGlzLndpZHRoID0gMC4xNSkgKwogIGdlb21fcGFyYWxsZWxfc2V0c19heGVzKGFlcyhmaWxsID0geSksIGNvbG9yID0gJ2JsYWNrJywgYXhpcy53aWR0aCA9IDAuMSkgKwogIGdlb21fdGV4dCgKICAgIGFlcyh5ID0gbiwgc3BsaXQgPSB5KSwgc3RhdCA9ICdwYXJhbGxlbF9zZXRzX2F4ZXMnLCBmb250ZmFjZSA9ICdib2xkJywKICAgIGhqdXN0ID0gZGF0YV9sYWJlbHMkaGp1c3QsIG51ZGdlX3ggPSBkYXRhX2xhYmVscyRudWRnZV94CiAgKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCdTYW1wbGUnLCdDbHVzdGVyJykpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcl9hc3NpZ25tZW50cykgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcsIGNvbG91ciA9ICdibGFjaycsIHNpemUgPSAxNSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKQogICkKCmNsdXN0ZXJzIDwtIGxldmVscyhzZXVyYXRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycykKY2VsbF90eXBlcyA8LSBzb3J0KHVuaXF1ZShzZXVyYXRAbWV0YS5kYXRhJGNlbGxfdHlwZV9zaW5nbGVyX2JsdWVwcmludGVuY29kZV9tYWluKSkKCmNvbG9yX2Fzc2lnbm1lbnRzIDwtIHNldE5hbWVzKAogIGMoY3VzdG9tX2NvbG9ycyRkaXNjcmV0ZVsxOmxlbmd0aChjbHVzdGVycyldLCBjdXN0b21fY29sb3JzJGRpc2NyZXRlWzE6bGVuZ3RoKGNlbGxfdHlwZXMpXSksCiAgYyhjbHVzdGVycyxjZWxsX3R5cGVzKQopCgpkYXRhIDwtIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgZHBseXI6OnJlbmFtZShjZWxsX3R5cGUgPSBjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbikgJT4lCiAgZHBseXI6Om11dGF0ZShjZWxsX3R5cGUgPSBmYWN0b3IoY2VsbF90eXBlLCBsZXZlbHMgPSBjZWxsX3R5cGVzKSkgJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBjZWxsX3R5cGUpICU+JQogIHRhbGx5KCkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdhdGhlcl9zZXRfZGF0YSgxOjIpICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICB4ID0gZmFjdG9yKHgsIGxldmVscyA9IHVuaXF1ZSh4KSksCiAgICB5ID0gZmFjdG9yKHksIGxldmVscyA9IGMoY2x1c3RlcnMsY2VsbF90eXBlcykpCiAgKQoKZGF0YV9sYWJlbHMgPC0gdGliYmxlKAogICAgZ3JvdXAgPSBjKAogICAgICByZXAoJ3NldXJhdF9jbHVzdGVycycsIGxlbmd0aChjbHVzdGVycykpLAogICAgICByZXAoJ2NlbGxfdHlwZScsIGxlbmd0aChjZWxsX3R5cGVzKSkKICAgICkKICkgJT4lCiAgbXV0YXRlKAogICAgaGp1c3QgPSBpZmVsc2UoZ3JvdXAgPT0gJ3NldXJhdF9jbHVzdGVycycsIDEsIDApLAogICAgbnVkZ2VfeCA9IGlmZWxzZShncm91cCA9PSAnc2V1cmF0X2NsdXN0ZXJzJywgLTAuMSwgMC4xKQogICkKCnAyIDwtIGdncGxvdChkYXRhLCBhZXMoeCwgaWQgPSBpZCwgc3BsaXQgPSB5LCB2YWx1ZSA9IG4pKSArCiAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gc2V1cmF0X2NsdXN0ZXJzKSwgYWxwaGEgPSAwLjc1LCBheGlzLndpZHRoID0gMC4xNSkgKwogIGdlb21fcGFyYWxsZWxfc2V0c19heGVzKGFlcyhmaWxsID0geSksIGNvbG9yID0gJ2JsYWNrJywgYXhpcy53aWR0aCA9IDAuMSkgKwogIGdlb21fdGV4dCgKICAgIGFlcyh5ID0gbiwgc3BsaXQgPSB5KSwgc3RhdCA9ICdwYXJhbGxlbF9zZXRzX2F4ZXMnLCBmb250ZmFjZSA9ICdib2xkJywKICAgIGhqdXN0ID0gZGF0YV9sYWJlbHMkaGp1c3QsIG51ZGdlX3ggPSBkYXRhX2xhYmVscyRudWRnZV94CiAgKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCdDbHVzdGVyJywnQ2VsbCB0eXBlJykpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcl9hc3NpZ25tZW50cykgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcsIGNvbG91ciA9ICdibGFjaycsIHNpemUgPSAxNSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKQogICkKCmdnc2F2ZSgKICAncGxvdHMvc2FtcGxlc19jbHVzdGVyc19jZWxsX3R5cGVzX2FsbHV2aWFsLnBuZycsCiAgcDEgKyBwMiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKSwKICBoZWlnaHQgPSA2LCB3aWR0aCA9IDgKKQoKYGBgCgpgYGB7cn0KI1VNQVAgYnkgY2VsbCB0eXBlClVNQVBfY2VudGVyc19jZWxsX3R5cGUgPC0gdGliYmxlKAogICAgVU1BUF8xID0gYXMuZGF0YS5mcmFtZShzZXVyYXRAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5ncykkVU1BUF8xLAogICAgVU1BUF8yID0gYXMuZGF0YS5mcmFtZShzZXVyYXRAcmVkdWN0aW9ucyRVTUFQQGNlbGwuZW1iZWRkaW5ncykkVU1BUF8yLAogICAgY2VsbF90eXBlID0gc2V1cmF0QG1ldGEuZGF0YSRjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbgogICkgJT4lCiAgZ3JvdXBfYnkoY2VsbF90eXBlKSAlPiUKICBzdW1tYXJpemUoeCA9IG1lZGlhbihVTUFQXzEpLCB5ID0gbWVkaWFuKFVNQVBfMikpCgpwIDwtIGJpbmRfY29scyhzZXVyYXRAbWV0YS5kYXRhLCBhcy5kYXRhLmZyYW1lKHNldXJhdEByZWR1Y3Rpb25zJFVNQVBAY2VsbC5lbWJlZGRpbmdzKSkgJT4lCiAgZ2dwbG90KGFlcyhVTUFQXzEsIFVNQVBfMiwgY29sb3IgPSBjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbikpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjIpICsKICBnZW9tX2xhYmVsKAogICAgZGF0YSA9IFVNQVBfY2VudGVyc19jZWxsX3R5cGUsCiAgICBtYXBwaW5nID0gYWVzKHgsIHksIGxhYmVsID0gY2VsbF90eXBlKSwKICAgIHNpemUgPSAzLjUsCiAgICBmaWxsID0gJ3doaXRlJywKICAgIGNvbG9yID0gJ2JsYWNrJywKICAgIGZvbnRmYWNlID0gJ2JvbGQnLAogICAgYWxwaGEgPSAwLjUsCiAgICBsYWJlbC5zaXplID0gMCwKICAgIHNob3cubGVnZW5kID0gRkFMU0UKICApICsKICB0aGVtZV9idygpICsKICBleHBhbmRfbGltaXRzKHggPSBjKC0yMiwxNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9ycyRkaXNjcmV0ZSkgKwogIGxhYnMoY29sb3IgPSAnQ2VsbCB0eXBlJykgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMikpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ3JpZ2h0JykgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGFubm90YXRlKAogICAgZ2VvbSA9ICd0ZXh0JywgeCA9IEluZiwgeSA9IC1JbmYsCiAgICBsYWJlbCA9IHBhc3RlMCgnbiA9ICcsIGZvcm1hdChucm93KHNldXJhdEBtZXRhLmRhdGEpLCBiaWcubWFyayA9ICcsJywgdHJpbSA9IFRSVUUpKSwKICAgIHZqdXN0ID0gLTEuNSwgaGp1c3QgPSAxLjI1LCBjb2xvciA9ICdibGFjaycsIHNpemUgPSAyLjUKICApCgpnZ3NhdmUoCiAgJ3Bsb3RzL3VtYXBfYnlfY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4ucG5nJywKICBwLAogIGhlaWdodCA9IDQsCiAgd2lkdGggPSA2CikKYGBgCgpgYGB7cn0KI0RvdCBwbG90IChtdWx0aXBsZSBnZW5lcykKIyBjZWxscyB3aWxsIGJlIGdyb3VwZWQgYnkgc2FtcGxlcyB0aGF0IHRoZXkgaGF2ZSBiZWVuIGFzc2lnbmVkIHRvCmdyb3VwX2lkcyA8LSB1bmlxdWUoc2V1cmF0QG1ldGEuZGF0YSRjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbikKCiMgc2VsZWN0IGEgc2V0IG9mIGdlbmVzIGZvciB3aGljaCB3ZSB3YW50IHRvIHNob3cgZXhwcmVzc2lvbgpnZW5lc190b19zaG93IDwtIHNldXJhdEBtaXNjJG1hcmtlcl9nZW5lcyRjZXJlYnJvX3NldXJhdCRjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbiAlPiUKICBncm91cF9ieShjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbikgJT4lCiAgYXJyYW5nZShwX3ZhbF9hZGopICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMSkgJT4lCiAgYXJyYW5nZShjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbikgJT4lCiAgcHVsbChnZW5lKQoKIyBmb3IgZXZlcnkgc2FtcGxlLWdlbmUgY29tYmluYXRpb24sIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBleHByZXNzaW9uIGFjcm9zcwojIGFsbCBjZWxscyBhbmQgdGhlbiB0cmFuc2Zvcm0gdGhlIGRhdGEgaW50byBhIGRhdGEgZnJhbWUKZXhwcmVzc2lvbl9sZXZlbHNfcGVyX2dyb3VwIDwtIHZhcHBseSgKICAgIGdyb3VwX2lkcywgRlVOLlZBTFVFID0gbnVtZXJpYyhsZW5ndGgoZ2VuZXNfdG9fc2hvdykpLCBmdW5jdGlvbih4KSB7CiAgICAgIGNlbGxzX2luX2N1cnJlbnRfZ3JvdXAgPC0gd2hpY2goc2V1cmF0QG1ldGEuZGF0YSRjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbiA9PSB4KQogICAgICBNYXRyaXg6OnJvd01lYW5zKHNldXJhdEBhc3NheXMkU0NUQGRhdGFbZ2VuZXNfdG9fc2hvdyxjZWxsc19pbl9jdXJyZW50X2dyb3VwXSkKICAgIH0KICApICU+JQogIHQoKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKGNlbGxfdHlwZV9zaW5nbGVyX2JsdWVwcmludGVuY29kZV9tYWluID0gcm93bmFtZXMoLikpICU+JQogIHNlbGVjdChjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbiwgZXZlcnl0aGluZygpKSAlPiUKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gYygyOm5jb2woLikpLAogICAgbmFtZXNfdG8gPSAnZ2VuZScKICApICU+JQogIGRwbHlyOjpyZW5hbWUoZXhwcmVzc2lvbiA9IHZhbHVlKSAlPiUKICBtdXRhdGUoaWRfdG9fbWVyZ2UgPSBwYXN0ZTAoY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4sICdfJywgZ2VuZSkpCgojIGZvciBldmVyeSBzYW1wbGUtZ2VuZSBjb21iaW5hdGlvbiwgY2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIGNlbGxzIGluIHRoZQojIHJlc3BlY3RpdmUgZ3JvdXAgdGhhdCBoYXMgYXQgbGVhc3QgMSB0cmFuc2NyaXB0ICh0aGlzIG1lYW5zIHdlIGNvbnNpZGVyIGl0CiMgYXMgZXhwcmVzc2luZyB0aGUgZ2VuZSkgYW5kIHRoZW4gdHJhbnNmb3JtIHRoZSBkYXRhIGludG8gYSBkYXRhIGZyYW1lCnBlcmNlbnRhZ2Vfb2ZfY2VsbHNfZXhwcmVzc2luZ19nZW5lIDwtIHZhcHBseSgKICAgIGdyb3VwX2lkcywgRlVOLlZBTFVFID0gbnVtZXJpYyhsZW5ndGgoZ2VuZXNfdG9fc2hvdykpLCBmdW5jdGlvbih4KSB7CiAgICAgIGNlbGxzX2luX2N1cnJlbnRfZ3JvdXAgPC0gd2hpY2goc2V1cmF0QG1ldGEuZGF0YSRjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbiA9PSB4KQogICAgICBNYXRyaXg6OnJvd1N1bXMoc2V1cmF0QGFzc2F5cyRTQ1RAZGF0YVtnZW5lc190b19zaG93LGNlbGxzX2luX2N1cnJlbnRfZ3JvdXBdICE9IDApCiAgICB9CiAgKSAlPiUKICB0KCkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIG11dGF0ZShjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbiA9IHJvd25hbWVzKC4pKSAlPiUKICBzZWxlY3QoY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4sIGV2ZXJ5dGhpbmcoKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IGMoMjpuY29sKC4pKSwKICAgIG5hbWVzX3RvID0gJ2dlbmUnCiAgKSAlPiUKICBkcGx5cjo6cmVuYW1lKGNlbGxfY291bnQgPSB2YWx1ZSkgJT4lCiAgbGVmdF9qb2luKAogICAgLiwKICAgIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgICAgIGdyb3VwX2J5KGNlbGxfdHlwZV9zaW5nbGVyX2JsdWVwcmludGVuY29kZV9tYWluKSAlPiUKICAgICAgdGFsbHkoKSwKICAgIGJ5ID0gJ2NlbGxfdHlwZV9zaW5nbGVyX2JsdWVwcmludGVuY29kZV9tYWluJykgJT4lCiAgbXV0YXRlKAogICAgaWRfdG9fbWVyZ2UgPSBwYXN0ZTAoY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4sICdfJywgZ2VuZSksCiAgICBwZXJjZW50X2NlbGxzID0gY2VsbF9jb3VudCAvIG4KICApCgojIG1lcmdlIHRoZSB0d28gZGF0YSBmcmFtZXMgY3JlYXRlZCBiZWZvcmUgYW5kIHBsb3QgdGhlIGRhdGEKcCA8LSBsZWZ0X2pvaW4oCiAgICBleHByZXNzaW9uX2xldmVsc19wZXJfZ3JvdXAsCiAgICBwZXJjZW50YWdlX29mX2NlbGxzX2V4cHJlc3NpbmdfZ2VuZSAlPiUgc2VsZWN0KGlkX3RvX21lcmdlLCBwZXJjZW50X2NlbGxzKSwKICAgIGJ5ID0gJ2lkX3RvX21lcmdlJwogICkgJT4lCiAgbXV0YXRlKAogICAgY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4gPSBmYWN0b3IoY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4sIGxldmVscyA9IHJldihncm91cF9pZHMpKSwKICAgIGdlbmUgPSBmYWN0b3IoZ2VuZSwgbGV2ZWxzID0gZ2VuZXNfdG9fc2hvdykKICApICU+JQogIGFycmFuZ2UoZ2VuZSkgJT4lCiAgZ2dwbG90KGFlcyhnZW5lLCBjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGV4cHJlc3Npb24sIHNpemUgPSBwZXJjZW50X2NlbGxzKSkgKwogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcigKICAgIHBhbGV0dGUgPSAnUmVkcycsCiAgICBkaXJlY3Rpb24gPSAxLAogICAgbmFtZSA9ICdMb2ctbm9ybWFsaXNlZFxuZXhwcmVzc2lvbicsCiAgICBndWlkZSA9IGd1aWRlX2NvbG9yYmFyKGZyYW1lLmNvbG91ciA9ICJibGFjayIsIHRpY2tzLmNvbG91ciA9ICJibGFjayIpCiAgKSArCiAgc2NhbGVfc2l6ZShuYW1lID0gJ1BlcmNlbnRcbm9mIGNlbGxzJywgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgbGFicyh5ID0gJ0NlbGwgdHlwZScsIGNvbG9yID0gJ0V4cHJlc3Npb24nKSArCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkKCmdnc2F2ZSgncGxvdHMvbWFya2VyX2dlbmVzX2J5X2NlbGxfdHlwZV9hc19kb3RfcGxvdC5wbmcnLCBwLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDYpCmBgYAoKYGBge3J9Cmdyb3VwX2lkcyA8LSBsZXZlbHMoc2V1cmF0QG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMpCgpnZW5lc190b19zaG93IDwtIHNldXJhdEBtaXNjJG1hcmtlcl9nZW5lcyRjZXJlYnJvX3NldXJhdCRzZXVyYXRfY2x1c3RlcnMgJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICBhcnJhbmdlKHBfdmFsX2FkaikgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKSA9PSAxKSAlPiUKICBhcnJhbmdlKHNldXJhdF9jbHVzdGVycykgJT4lCiAgcHVsbChnZW5lKQoKZXhwcmVzc2lvbl9sZXZlbHNfcGVyX2dyb3VwIDwtIHZhcHBseSgKICAgIGdyb3VwX2lkcywgRlVOLlZBTFVFID0gbnVtZXJpYyhsZW5ndGgoZ2VuZXNfdG9fc2hvdykpLCBmdW5jdGlvbih4KSB7CiAgICAgIGNlbGxzX2luX2N1cnJlbnRfZ3JvdXAgPC0gd2hpY2goc2V1cmF0QG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMgPT0geCkKICAgICAgTWF0cml4Ojpyb3dNZWFucyhzZXVyYXRAYXNzYXlzJFNDVEBkYXRhW2dlbmVzX3RvX3Nob3csY2VsbHNfaW5fY3VycmVudF9ncm91cF0pCiAgICB9CiAgKSAlPiUKICB0KCkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIG11dGF0ZShjbHVzdGVyID0gcm93bmFtZXMoLikpICU+JQogIHNlbGVjdChjbHVzdGVyLCBldmVyeXRoaW5nKCkpICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSBjKDI6bmNvbCguKSksCiAgICBuYW1lc190byA9ICdnZW5lJwogICkgJT4lCiAgZHBseXI6OnJlbmFtZShleHByZXNzaW9uID0gdmFsdWUpICU+JQogIG11dGF0ZShpZF90b19tZXJnZSA9IHBhc3RlMChjbHVzdGVyLCAnXycsIGdlbmUpKQoKcGVyY2VudGFnZV9vZl9jZWxsc19leHByZXNzaW5nX2dlbmUgPC0gdmFwcGx5KAogICAgZ3JvdXBfaWRzLCBGVU4uVkFMVUUgPSBudW1lcmljKGxlbmd0aChnZW5lc190b19zaG93KSksIGZ1bmN0aW9uKHgpIHsKICAgICAgY2VsbHNfaW5fY3VycmVudF9ncm91cCA8LSB3aGljaChzZXVyYXRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVyID09IHgpCiAgICAgIE1hdHJpeDo6cm93U3VtcyhzZXVyYXRAYXNzYXlzJFNDVEBkYXRhW2dlbmVzX3RvX3Nob3csY2VsbHNfaW5fY3VycmVudF9ncm91cF0gIT0gMCkKICAgIH0KICApICU+JQogIHQoKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKGNsdXN0ZXIgPSByb3duYW1lcyguKSkgJT4lCiAgc2VsZWN0KGNsdXN0ZXIsIGV2ZXJ5dGhpbmcoKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IGMoMjpuY29sKC4pKSwKICAgIG5hbWVzX3RvID0gJ2dlbmUnCiAgKSAlPiUKICBkcGx5cjo6cmVuYW1lKGNlbGxfY291bnQgPSB2YWx1ZSkgJT4lCiAgbGVmdF9qb2luKAogICAgLiwKICAgIHNldXJhdEBtZXRhLmRhdGEgJT4lCiAgICAgIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycykgJT4lCiAgICAgIHRhbGx5KCkgJT4lCiAgICAgIGRwbHlyOjpyZW5hbWUoY2x1c3RlciA9IHNldXJhdF9jbHVzdGVycyksCiAgICBieSA9ICdjbHVzdGVyJykgJT4lCiAgbXV0YXRlKAogICAgaWRfdG9fbWVyZ2UgPSBwYXN0ZTAoY2x1c3RlciwgJ18nLCBnZW5lKSwKICAgIHBlcmNlbnRfY2VsbHMgPSBjZWxsX2NvdW50IC8gbgogICkKCnAgPC0gbGVmdF9qb2luKAogICAgZXhwcmVzc2lvbl9sZXZlbHNfcGVyX2dyb3VwLAogICAgcGVyY2VudGFnZV9vZl9jZWxsc19leHByZXNzaW5nX2dlbmUgJT4lIHNlbGVjdChpZF90b19tZXJnZSwgcGVyY2VudF9jZWxscyksCiAgICBieSA9ICdpZF90b19tZXJnZScKICApICU+JQogIG11dGF0ZSgKICAgIGNsdXN0ZXIgPSBmYWN0b3IoY2x1c3RlciwgbGV2ZWxzID0gcmV2KGdyb3VwX2lkcykpLAogICAgZ2VuZSA9IGZhY3RvcihnZW5lLCBsZXZlbHMgPSBnZW5lc190b19zaG93KQogICkgJT4lCiAgYXJyYW5nZShnZW5lKSAlPiUKICBnZ3Bsb3QoYWVzKGdlbmUsIGNsdXN0ZXIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBleHByZXNzaW9uLCBzaXplID0gcGVyY2VudF9jZWxscykpICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIoCiAgICBwYWxldHRlID0gJ1JlZHMnLAogICAgZGlyZWN0aW9uID0gMSwKICAgIG5hbWUgPSAnTG9nLW5vcm1hbGlzZWRcbmV4cHJlc3Npb24nLAogICAgZ3VpZGUgPSBndWlkZV9jb2xvcmJhcihmcmFtZS5jb2xvdXIgPSAiYmxhY2siLCB0aWNrcy5jb2xvdXIgPSAiYmxhY2siKQogICkgKwogIHNjYWxlX3NpemUobmFtZSA9ICdQZXJjZW50XG5vZiBjZWxscycsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIGxhYnMoeSA9ICdDbHVzdGVyJywgY29sb3IgPSAnRXhwcmVzc2lvbicpICsKICBjb29yZF9maXhlZCgpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApCgpnZ3NhdmUoJ3Bsb3RzL21hcmtlcl9nZW5lc19ieV9jbHVzdGVyX2FzX2RvdF9wbG90LnBuZycsIHAsIGhlaWdodCA9IDcsIHdpZHRoID0gOCkKYGBgCgpgYGB7cn0KI0dlbmUgc2V0IGVucmljaG1lbnQgYW5hbHlzaXMKIyBmdW5jdGlvbiB0byByZWFkIEdNVCBmaWxlCnJlYWRfR01UX2ZpbGUgPC0gZnVuY3Rpb24oZmlsZSkgewogIGdtdCA8LSByZWFkcjo6cmVhZF9kZWxpbSgKICAgIGZpbGUsCiAgICBkZWxpbSA9ICc7JywKICAgIGNvbF9uYW1lcyA9IGMoJ1gxJyksCiAgICBjb2xfdHlwZXMgPSByZWFkcjo6Y29scygpCiAgKQoKICBnZW5lX3NldF9nZW5lcyA8LSBsaXN0KCkKICBmb3IgKCBpIGluIHNlcV9sZW4obnJvdyhnbXQpKSApCiAgewogICAgdGVtcF9nZW5lcyA8LSBzdHJzcGxpdChnbXQkWDFbaV0sIHNwbGl0ID0gJ1x0JylbWzFdXSAlPiUgdW5saXN0KCkKICAgIHRlbXBfZ2VuZXMgPC0gdGVtcF9nZW5lc1szOmxlbmd0aCh0ZW1wX2dlbmVzKV0KICAgIGdlbmVfc2V0X2dlbmVzW1tpXV0gPC0gdGVtcF9nZW5lcwogIH0KICBnZW5lX3NldF9sb2FkZWQgPC0gbGlzdCgKICAgIGdlbmVzZXRzID0gZ2VuZV9zZXRfZ2VuZXMsCiAgICBnZW5lc2V0Lm5hbWVzID0gbGFwcGx5KHN0cnNwbGl0KGdtdCRYMSwgc3BsaXQgPSAnXHQnKSwgJ1snLCAxKSAlPiUgdW5saXN0KCksCiAgICBnZW5lc2V0LmRlc2NyaXB0aW9uID0gbGFwcGx5KAogICAgICAgIHN0cnNwbGl0KGdtdCRYMSwgc3BsaXQgPSAnXHQnKSwgJ1snLCAyCiAgICAgICkgJT4lIHVubGlzdCgpCiAgKQoKICByZXR1cm4oZ2VuZV9zZXRfbG9hZGVkKQp9CgojIGxvYWQgZ2VuZSBzZXRzIGZyb20gR01UIGZpbGUKZ2VuZV9zZXRzIDwtIHJlYWRfR01UX2ZpbGUoJ2guYWxsLnY3LjIuc3ltYm9scy5nbXQnKQoKIyBzZXQgZ2VuZSBzZXQgbmFtZXMKbmFtZXMoZ2VuZV9zZXRzJGdlbmVzZXRzKSA8LSBnZW5lX3NldHMkZ2VuZXNldC5uYW1lcwoKIyBnZXQgaW5kaWNlcyBvZiBjZWxscyB3aGljaCBhcmUgZWl0aGVyIEhTQyBvciBtb25vY3l0ZXMKY2VsbHNfdG9fYW5hbHl6ZSA8LSBzZXVyYXRAbWV0YS5kYXRhICU+JQogIG11dGF0ZShyb3dfbnVtYmVyID0gcm93X251bWJlcigpKSAlPiUKICBmaWx0ZXIoZ3JlcGwoY2VsbF90eXBlX3NpbmdsZXJfYmx1ZXByaW50ZW5jb2RlX21haW4sIHBhdHRlcm4gPSAnSFNDfE1vbm9jeXRlcycpKSAlPiUKICBhcnJhbmdlKGNlbGxfdHlwZV9zaW5nbGVyX2JsdWVwcmludGVuY29kZV9tYWluKSAlPiUKICBwdWxsKHJvd19udW1iZXIpCgojIGdldCBsaXN0IG9mIGdlbmVzIHVuaXF1ZSBnZW5lcyBhY3Jvc3MgYWxsIGdlbmUgc2V0cwpnZW5lc190b19hbmFseXplIDwtIGdlbmVfc2V0cyRnZW5lc2V0cyAlPiUgdW5saXN0KCkgJT4lIHVuaXF1ZSgpCiMgZmlsdGVyIGdlbmUgbGlzdCBmb3IgdGhvc2Ugd2hpY2ggYXJlIHByZXNlbnQgaW4gdGhlIGRhdGEgc2V0CmdlbmVzX3RvX2FuYWx5emUgPC0gZ2VuZXNfdG9fYW5hbHl6ZVt3aGljaChnZW5lc190b19hbmFseXplICVpbiUgcm93bmFtZXMoc2V1cmF0QGFzc2F5cyRTQ1RAY291bnRzKSldCgojIGdldCBleHByZXNzaW9uIG1hdHJpeCBhbmQgcmVkdWNlIGl0IHRvIGNlbGxzIGFuZCBnZW5lcyBvZiBpbnRlcmVzdApleHByZXNzaW9uX21hdHJpeCA8LSBzZXVyYXRAYXNzYXlzJFNDVEBjb3VudHNbIGdlbmVzX3RvX2FuYWx5emUgLCBjZWxsc190b19hbmFseXplXSAlPiUgYXMubWF0cml4KCkKCiMgcGVyZm9ybSBHU1ZBCmdzdmEgPC0gR1NWQTo6Z3N2YSgKICBleHByZXNzaW9uX21hdHJpeCwKICBnc2V0LmlkeC5saXN0ID0gZ2VuZV9zZXRzJGdlbmVzZXRzLAogIHBhcmFsbGVsLnN6ID0gMQopCgojIGxvYWQgbGltbWEgbGlicmFyeSBmb3Igc3RhdGlzdGljYWwgdGVzdGluZwpsaWJyYXJ5KGxpbW1hKQoKIyBnZW5lcmF0ZSBkZXNpZ24gbWF0cml4CmRlc2lnbl9tYXRyaXggPC0gdGliYmxlKAogIGNvbnRyb2wgPSAxLAogIHRlc3QgPSBjKAogICAgcmVwKDAsIHNldXJhdEBtZXRhLmRhdGEgJT4lIGZpbHRlcihjZWxsX3R5cGVfc2luZ2xlcl9ibHVlcHJpbnRlbmNvZGVfbWFpbiA9PSAnSFNDJykgJT4lIG5yb3coKSksCiAgICByZXAoMSwgc2V1cmF0QG1ldGEuZGF0YSAlPiUgZmlsdGVyKGNlbGxfdHlwZV9zaW5nbGVyX2JsdWVwcmludGVuY29kZV9tYWluID09ICdNb25vY3l0ZXMnKSAlPiUgbnJvdygpKQogICkKKQoKaGVhZChkZXNpZ25fbWF0cml4KQojIEEgdGliYmxlOiA2IHggMgojICAgY29udHJvbCAgdGVzdAojICAgICA8ZGJsPiA8ZGJsPgojIDEgICAgICAgMSAgICAgMAojIDIgICAgICAgMSAgICAgMAojIDMgICAgICAgMSAgICAgMAojIDQgICAgICAgMSAgICAgMAojIDUgICAgICAgMSAgICAgMAojIDYgICAgICAgMSAgICAgMAoKIyBmaXQgbGluZWFyIG1vZGVsLCBmb2xsb3dlZCBieSBlbXBpcmljYWwgQmF5ZXMgc3RhdGlzdGljcyBmb3IgZGlmZmVyZW50aWFsCiMgZW5yaWNobWVudCBhbmFseXNpcwpmaXQgPC0gbG1GaXQoZ3N2YSwgZGVzaWduX21hdHJpeCkKZml0IDwtIGVCYXllcyhmaXQpCgojIHByZXBhcmUgZGF0YSBmb3IgcGxvdHRpbmcKZGF0YSA8LSB0b3BUYWJsZShmaXQsIGNvZWYgPSAndGVzdCcsIG51bWJlciA9IDUwKSAlPiUKICBtdXRhdGUoZ2VuZV9zZXQgPSByb3duYW1lcyhmaXQkdCkpICU+JQogIGFycmFuZ2UodCkgJT4lCiAgbXV0YXRlKAogICAgZ2VuZV9zZXQgPSBmYWN0b3IoZ2VuZV9zZXQsIGxldmVscyA9IGdlbmVfc2V0KSwKICAgIGp1c3QgPSBpZmVsc2UodCA8IDAsIDAsIDEpLAogICAgbnVkZ2VfeSA9IGlmZWxzZSh0IDwgMCwgMSwgLTEpLAogICAgY29sb3IgPSBpZmVsc2UodCA8IC01IHwgdCA+IDUsICdibGFjaycsICdncmV5JykKICApCgpEYXRhRnJhbWUoZGF0YSkKIyBEYXRhRnJhbWUgd2l0aCA1MCByb3dzIGFuZCAxMCBjb2x1bW5zCiMgICAgICAgICBsb2dGQyAgICAgQXZlRXhwciAgICAgICAgIHQgICAgICBQLlZhbHVlICAgIGFkai5QLlZhbCAgICAgICAgIEIgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXQgICAgICBqdXN0ICAgbnVkZ2VfeSAgICAgICBjb2xvcgojICAgICA8bnVtZXJpYz4gICA8bnVtZXJpYz4gPG51bWVyaWM+ICAgIDxudW1lcmljPiAgICA8bnVtZXJpYz4gPG51bWVyaWM+ICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxmYWN0b3I+IDxudW1lcmljPiA8bnVtZXJpYz4gPGNoYXJhY3Rlcj4KIyAxICAgLTAuMzY5NDQzICAtMC4xNTY5NjkwICAtMzUuMTYxOSAxLjI2MDY2ZS0xODYgMS4wNTA1NWUtMTg1ICAgNDE1Ljk4MCAgSEFMTE1BUktfVEdGX0JFVEFfU0lHTkFMSU5HICAgICAgICAgICAgICAgIDAgICAgICAgICAxICAgICAgIGJsYWNrCiMgMiAgIC0wLjI1MTQxMyAgLTAuMDgyMDAxMiAgLTI3LjIyNjEgMS45NjE1MmUtMTI3IDguOTE1OThlLTEyNyAgIDI3OS43NjEgIEhBTExNQVJLX05PVENIX1NJR05BTElORyAgICAgICAgICAgICAgICAgICAwICAgICAgICAgMSAgICAgICBibGFjawojIDMgICAtMC4yODgxNjkgIC0wLjEyMDIyOTMgIC0yNi4xNDY0IDEuNDA3MTJlLTExOSA1LjQxMjAxZS0xMTkgICAyNjEuNjg5ICBIQUxMTUFSS19FU1RST0dFTl9SRVNQT05TRV9FQVJMWSAgICAgICAgICAgMCAgICAgICAgIDEgICAgICAgYmxhY2sKIyA0ICAgLTAuMTM1MDM0ICAtMC4xNTExMTQ2ICAtMjIuMzU2NSAgOC41MDE5NGUtOTMgIDIuMzYxNjVlLTkyICAgMjAwLjA5NCAgSEFMTE1BUktfSU5URVJGRVJPTl9BTFBIQV9SRVNQT05TRSAgICAgICAgIDAgICAgICAgICAxICAgICAgIGJsYWNrCiMgNSAgIC0wLjIwMzY1MSAgLTAuMTA0OTQ5OCAgLTIyLjA3MjggIDcuNDQwMDZlLTkxICAxLjk1NzkxZS05MCAgIDE5NS42MjggIEhBTExNQVJLX0lOVEVSRkVST05fR0FNTUFfUkVTUE9OU0UgICAgICAgICAwICAgICAgICAgMSAgICAgICBibGFjawojIC4uLiAgICAgICAuLi4gICAgICAgICAuLi4gICAgICAgLi4uICAgICAgICAgIC4uLiAgICAgICAgICAuLi4gICAgICAgLi4uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLi4uICAgICAgIC4uLiAgICAgICAuLi4gICAgICAgICAuLi4KIyA0NiAgIDAuMjAwMjU5ICAwLjI1MzQxNTk0ICAgMzUuOTI1MCAyLjMxOTAxZS0xOTIgMi4zMTkwMWUtMTkxICAgNDI5LjE4MiBIQUxMTUFSS19XTlRfQkVUQV9DQVRFTklOX1NJR05BTElORyAgICAgICAgIDEgICAgICAgIC0xICAgICAgIGJsYWNrCiMgNDcgICAwLjI5MzExMyAtMC4wODExNTYxMCAgIDM2LjE5OTUgMi4wMDc4NGUtMTk0IDIuNTA5ODBlLTE5MyAgIDQzMy45MjkgSEFMTE1BUktfTUlUT1RJQ19TUElORExFICAgICAgICAgICAgICAgICAgICAxICAgICAgICAtMSAgICAgICBibGFjawojIDQ4ICAgMC4zMzg0NDkgIDAuMTE1ODM3NzQgICAzNi4yNTYwIDcuNTYxOTZlLTE5NSAxLjI2MDMzZS0xOTMgICA0MzQuOTA2IEhBTExNQVJLX0NIT0xFU1RFUk9MX0hPTUVPU1RBU0lTICAgICAgICAgICAgMSAgICAgICAgLTEgICAgICAgYmxhY2sKIyA0OSAgIDAuMjUxNjc4ICAwLjAwMjYyMTM2ICAgMzcuNTExNyAyLjg2NzcyZS0yMDQgNy4xNjkzMGUtMjAzICAgNDU2LjU5MiBIQUxMTUFSS19IWVBPWElBICAgICAgICAgICAgICAgICAgICAgICAgICAgIDEgICAgICAgIC0xICAgICAgIGJsYWNrCiMgNTAgICAwLjI4MjkwNyAtMC4wNTAyMTQwNCAgIDQ4LjU5NzEgMS43MjUyM2UtMjg1IDguNjI2MTZlLTI4NCAgIDY0My41NzEgSEFMTE1BUktfVE5GQV9TSUdOQUxJTkdfVklBX05GS0IgICAgICAgICAgICAxICAgICAgICAtMSAgICAgICBibGFjawoKIyBwbG90IHQtdmFsdWUKcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4ID0gZ2VuZV9zZXQsIHkgPSB0LCBmaWxsID0gdCkpICsKICBnZW9tX2NvbCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBjKC01LDUpLCBsaW5ldHlwZSA9ICdkYXNoZWQnLCBjb2xvciA9ICdncmV5ODAnKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKAogICAgICB4ID0gZ2VuZV9zZXQsCiAgICAgIHkgPSAwLAogICAgICBsYWJlbCA9IGdlbmVfc2V0LAogICAgICBoanVzdCA9IGp1c3QsCiAgICAgIGNvbG9yID0gY29sb3IKICAgICksCiAgICBudWRnZV95ID0gZGF0YSRudWRnZV95LCBzaXplID0gMwogICkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICcnLCBsYWJlbHMgPSBOVUxMKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAndC12YWx1ZScsIGxpbWl0cyA9IGMoLTU1LDU1KSkgKwogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAnU3BlY3RyYWwnLCBsaW1pdHMgPSBjKC1tYXgoZGF0YSR0KSwgbWF4KGRhdGEkdCkpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoJ2JsYWNrJyA9ICdibGFjaycsICdncmV5JyA9ICdncmV5JykpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9ICBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAnbm9uZScKICApCgpnZ3NhdmUoJ3Bsb3RzL2dzdmFfaGFsbG1hcmtfZ2VuZV9zZXRzX21vbm9jeXRlc192c19oc2MucG5nJywgaGVpZ2h0ID0gNy41LCB3aWR0aCA9IDcuNSkKCmBgYAoKYGBge3J9CiMjSGVhdG1hcApsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KGFyZ3BhcnNlKQoKaGVhdG1hcCA8LSBmdW5jdGlvbiAoTWF0cml4LGxhYmVscyl7CiAgICAgcGxvdF9oZWF0bWFwIDwtIHBoZWF0bWFwKE1hdHJpeCxzY2FsZT0nbm9uZScsY2x1c3Rlcl9jb2xzID0gRixjbHVzdGVyX3Jvd3MgPSBGLGNlbGx3aWR0aCA9IDIwLGNlbGxoZWlnaHQgPSAyMCxjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoY29sb3JzID0gYygid2hpdGUiLCJyZWQiKSkoMTAwKSxkaXNwbGF5X251bWJlcnM9bGFiZWxzKQogICAgIHJldHVybihwbG90X2hlYXRtYXApCn0KCmdldFNpZyA8LSBmdW5jdGlvbihkYykgewppZihkYyA+IHB2YWx1ZSl7c2MgPC0gIiJ9CmlmKGRjIDwgcHZhbHVlKXtzYyA8LSAiKiJ9fQoKcGFyc2VyID0gQXJndW1lbnRQYXJzZXIoKQpwYXJzZXIkYWRkX2FyZ3VtZW50KCItLUxSX3Njb3JlIiwgaGVscD0idGhlIGZpbGUgb2Ygc2FtcGxlLkxSLnNjb3JlLnhscyIscmVxdWlyZWQgPVQpCnBhcnNlciRhZGRfYXJndW1lbnQoIi0tcHZhbHVlIiwgaGVscD0idGhlIGZpbGUgb2Ygc2FtcGxlLkxSLnNjb3JlLnhscyIsZGVmYXVsdCA9ICcwLjEnKQpwYXJzZXIkYWRkX2FyZ3VtZW50KCItLW91dGRpciIsIGhlbHA9InRoZSBvdXRkaXIiLGRlZmF1bHQ9Jy4nKQphcmdzIDwtIHBhcnNlciRwYXJzZV9hcmdzKCkKc3RyKGFyZ3MpCgpMUl9zY29yZSA9IGFyZ3MkTFJfc2NvcmUKcHZhbHVlID0gYXJncyRwdmFsdWUKb3V0ZGlyID0gYXJncyRvdXRkaXIKCnB2YWx1ZSA8LSBhcy5udW1lcmljKHB2YWx1ZSkKCmlmKCFkaXIuZXhpc3RzKG91dGRpcikpewogIGRpci5jcmVhdGUob3V0ZGlyKQp9CgpzZXR3ZChvdXRkaXIpCmZpbGUgPC0gcmVhZC50YWJsZShMUl9zY29yZSxzZXA9J1x0JyxoZWFkZXI9VCxyb3cubmFtZXM9MSxjaGVjay5uYW1lcz1GKQpsZW4gPC0gYXMubnVtZXJpYyhsZW5ndGgoY29sbmFtZXMoZmlsZSkpKQpNYXggPC0gbWF4KGFzLm51bWVyaWMoc2FwcGx5KHN0cnNwbGl0KGNvbG5hbWVzKGZpbGUpLCdfJyksIlsiLDIpWzE6bGVuLzJdKSkKCmZvciAoaSBpbiAxOk1heCl7CmluZGV4X3Njb3JlIDwtIGFzLmFycmF5KHdoaWNoKHNhcHBseShzdHJzcGxpdChjb2xuYW1lcyhmaWxlKSwnXycpLCJbIiwxKSA9PSBwYXN0ZTAoJ2NsdXN0ZXInLGFzLmNoYXJhY3RlcihpKSkpKQppbmRleF9wdmFsdWUgPC0gYXMuYXJyYXkod2hpY2goc2FwcGx5KHN0cnNwbGl0KGNvbG5hbWVzKGZpbGUpLCdfJyksIlsiLDIpID09IHBhc3RlMCgncHZhbHVlJyxhcy5jaGFyYWN0ZXIoaSkpKSkKaW5kZXggPC0gYyhpbmRleF9zY29yZSxpbmRleF9wdmFsdWUpCk1hdHJpeCA8LSBmaWxlWyxpbmRleF0KTFIgPC0gcm93bmFtZXMoYXMubWF0cml4KHNvcnQoYXBwbHkoTWF0cml4LDEsc3VtKSxkZWNyZWFzaW5nPVQpWzE6MzBdKSkKTWF0cml4X0xSIDwtIE1hdHJpeFtMUixdCmxhYmVsIDwtIGFzLm1hdHJpeChzYXBwbHkoTWF0cml4X0xSWyxhcy5udW1lcmljKE1heCsxKTphcy5udW1lcmljKGxlbmd0aChjb2xuYW1lcyhNYXRyaXhfTFIpKSldLGZ1bmN0aW9uKHgpe2lmZWxzZSh4PnB2YWx1ZSxzYzwtIiIsc2M8LSIqIil9KSxuY29sPU1heCkKcGRmKHBhc3RlMCgnY2x1c3RlcicsaSwnX290aGVyc19MUi5wZGYnKSx3aWR0aD02LGhlaWdodD0xNCkKaGVhdG1hcChNYXRyaXhfTFJbLDE6TWF4XSxsYWJlbCkKZGV2Lm9mZigpCnBuZyhwYXN0ZTAoJ2NsdXN0ZXInLGksJ19vdGhlcnNfTFIucG5nJyksd2lkdGg9NioyMDAsaGVpZ2h0PTE0KjIwMCxyZXM9MjAwLHR5cGUgPSAnY2FyaW8tcG5nJykKZGV2Lm9mZigpCn0KYGBgCgoKCg==