<commit_before>enabled(){
  let me = this;
  
  // configuration
  
  this.config = {
    templates: {} // identifier: { name, contents }
  };
  
  const configFile = "config.json";
  
  $TDP.checkFileExists(this.$token, configFile).then(exists => {
    if (!exists){
      $TDP.writeFile(this.$token, configFile, JSON.stringify(this.config));
    }
    else{
      $TDP.readFile(this.$token, configFile, true).then(contents => {
        try{
          $.extend(true, this.config, JSON.parse(contents));
        }catch(err){
          // why :(
        }
      }).catch(err => {
        $TD.alert("error", "Problem loading configuration for the template plugin: "+err.message);
      });
    }
  });
  
  this.saveConfig = () => {
    $TDP.writeFile(this.$token, configFile, JSON.stringify(this.config)).catch(err => {
      $TD.alert("error", "Problem saving configuration for the template plugin: "+err.message);
    });
  };
  
  // button
  
  var buttonHTML = '<button class="manage-templates-btn needsclick btn btn-on-blue full-width txt-left margin-b--12 padding-v--9"><i class="icon icon-bookmark"></i><span class="label padding-ls">Manage templates</span></button>';
  
  this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
  TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="js-tweet-type-button">', buttonHTML+'<div class="js-tweet-type-button">');
  
  var dockedComposePanel = $(".js-docked-compose");
  
  if (dockedComposePanel.length){
    dockedComposePanel.find(".js-tweet-type-button").first().before(buttonHTML);
  }
  
  // css
  
  this.css = window.TDPF_createCustomStyle(this);
  this.css.insert(".manage-templates-btn.active { color: #fff; box-shadow: 0 0 2px 3px #50a5e6; outline: 0; }");
  
  this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
  this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
  this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
  this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; text-align: right; }");
  this.css.insert(".templates-modal-bottom button { margin-left: 4px; }");
  
  this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
  this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
  this.css.insert(".template-list li { display: block; width: 100%; padding: 4px 8px; box-sizing: border-box; }");
  this.css.insert(".template-list li[data-template] { cursor: pointer; }");
  this.css.insert(".template-list li[data-template]:hover { background-color: #d8d8d8; }");
  this.css.insert(".template-list li span { white-space: nowrap; }");
  this.css.insert(".template-list li .icon { opacity: 0.6; margin-left: 4px; padding: 3px; }");
  this.css.insert(".template-list li .icon:hover { opacity: 1; }");
  this.css.insert(".template-list li .template-actions { float: right; }");
  
  this.css.insert(".template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
  this.css.insert(".template-editor-form { flex: 1 1 auto; padding: 12px 16px; font-size: 14px; overflow-y: auto; }");
  this.css.insert(".template-editor-form .compose-text-title { margin: 24px 0 9px; }");
  this.css.insert(".template-editor-form .compose-text-title:first-child { margin-top: 0; }");
  this.css.insert(".template-editor-form input, .template-editor-form textarea { color: #111; background-color: #fff; border: none; border-radius: 0; }");
  this.css.insert(".template-editor-form input:focus, .template-editor-form textarea:focus { box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6); }");
  this.css.insert(".template-editor-form textarea { height: 146px; font-size: 14px; padding: 10px; resize: none; }");
  this.css.insert(".template-editor-form .template-editor-tips-button { cursor: pointer; }");
  this.css.insert(".template-editor-form .template-editor-tips-button .icon { font-size: 12px; vertical-align: -5%; margin-left: 4px; }");
  this.css.insert(".template-editor-form .template-editor-tips { display: none; }");
  this.css.insert(".template-editor-form .template-editor-tips p { margin: 0 0 10px; }");
  this.css.insert(".template-editor-form .template-editor-tips li:nth-child(2n+1) { margin-top: 5px; padding-left: 6px; font-family: monospace; }");
  this.css.insert(".template-editor-form .template-editor-tips li:nth-child(2n) { margin-top: 1px; padding-left: 14px; opacity: 0.66; }");
  
  this.css.insert(".invisible { display: none !important; }");
  
  // template implementation
  
  var readTemplateTokens = (contents, tokenData) => {
    let currentIndex = -1;
    let startIndex = -1;
    let endIndex = -1;
    
    let data = [];
    let tokenNames = Object.keys(tokenData);
    
    while((currentIndex = contents.indexOf('{', currentIndex)) !== -1){
      startIndex = currentIndex+1;
      
      for(; startIndex < contents.length; startIndex++){
        if (!tokenNames.some(name => contents[startIndex] === name[startIndex-currentIndex-1])){
          break;
        }
      }
      
      endIndex = startIndex;
      
      let token = contents.substring(currentIndex+1, startIndex);
      let replacement = tokenData[token] || "";
      
      let entry = [ token, currentIndex ];
      
      if (contents[endIndex] === '#'){
        ++endIndex;
        
        let bracketCount = 1;
        
        for(; endIndex < contents.length; endIndex++){
          if (contents[endIndex] === '{'){
            ++bracketCount;
          }
          else if (contents[endIndex] === '}'){
            if (--bracketCount === 0){
              entry.push(contents.substring(startIndex+1, endIndex));
              break;
            }
          }
          else if (contents[endIndex] === '#'){
            entry.push(contents.substring(startIndex+1, endIndex));
            startIndex = endIndex;
          }
          else if (contents[endIndex] === '\\'){
            ++endIndex;
          }
        }
      }
      else if (contents[endIndex] !== '}'){
        continue;
      }
      
      data.push(entry);
      
      contents = contents.substring(0, currentIndex)+replacement+contents.substring(endIndex+1);
      currentIndex += replacement.length;
    }
    
    return [ contents, data ];
  };
  
  var doAjaxRequest = (url, evaluator) => {
    return new Promise((resolve, reject) => {
      $.get(url, function(data){
        if (evaluator){
          resolve(eval(evaluator.replace(/\$/g, "'"+data.replace(/(["'\\\n\r\u2028\u2029])/g, "\\$1")+"'")));
        }
        else{
          resolve(data);
        }
      }, "text").fail(function(){
        resolve("");
      });
    });
  };
  
  var useTemplate = (contents, append) => {
    let ele = $(".js-compose-text");
    if (ele.length === 0)return;
    
    let value = append ? ele.val()+contents : contents;
    let prevLength = value.length;
    
    let tokens = null;
    
    [value, tokens] = readTemplateTokens(value, {
      "cursor": "",
      "ajax": "(...)"
    });
    
    ele.val(value);
    ele.focus();
    
    ele[0].selectionStart = ele[0].selectionEnd = value.length;
    
    let promises = [];
    let indexOffset = 0;
    
    for(let token of tokens){
      switch(token[0]){
        case "cursor":
          let [, index1, length ] = token;
          ele[0].selectionStart = index1;
          ele[0].selectionEnd = index1+(length | 0 || 0);
          break;
          
        case "ajax":
          let [, index2, evaluator, url ] = token;
          
          if (!url){
            url = evaluator;
            evaluator = null;
          }

          promises.push(doAjaxRequest(url, evaluator).then(result => {
            const placeholderLen = 5; // "(...)".length
            
            let diff = result.length-placeholderLen;
            let realIndex = indexOffset+index2;
            
            let val = ele.val();
            ele.val(val.substring(0, realIndex)+result+val.substring(realIndex+placeholderLen));
            
            indexOffset += diff;
          }));
          
          break;
      }
    }
    
    if (promises.length > 0){
      let selStart = ele[0].selectionStart;
      let selEnd = ele[0].selectionEnd;
      
      ele.prop("disabled", true);
      
      Promise.all(promises).then(() => {
        ele.prop("disabled", false);
        ele.focus();
        
        ele[0].selectionStart = selStart+indexOffset;
        ele[0].selectionEnd = selEnd+indexOffset;
      });
    }
    
    if (!append){
      hideTemplateModal();
    }
  };
  
  // modal dialog
  
  this.editingTemplate = null;
  
  var showTemplateModal = () => {
    $(".manage-templates-btn").addClass("active");
    
    let html = `
<div class="templates-modal-wrap">
  <div class="templates-modal">
    <div class="template-list">
      <ul></ul>
      
      <div class="templates-modal-bottom">
        <button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
      </div>
    </div>

    <div class="template-editor invisible">
      <div class="template-editor-form">
        <div class="compose-text-title">Template Name</div>
        <input name="template-name" type="text">
        
        <div class="compose-text-title">Contents</div>
        <textarea name="template-contents" class="compose-text scroll-v scroll-styled-v scroll-styled-h scroll-alt"></textarea>

        <div class="compose-text-title template-editor-tips-button">Advanced <i class="icon icon-arrow-d"></i></div>
        <div class="template-editor-tips">
          <p>You can use the following tokens. All tokens except for <span style="font-family: monospace">{ajax}</span> can only be used once.</p>
          <ul>
            <li>{cursor}</li>
            <li>Location where the cursor is placed</li>
            <li>{cursor#&lt;selectionlength&gt;}</li>
            <li>Places cursor and selects a set amount of characters</li>
            <li>{paste}</li>
            <li>Paste text or an image from clipboard</li>
            <li>{paste#text}</li>
            <li>Paste only if clipboard has text</li>
            <li>{paste#image}</li>
            <li>Paste only if clipboard has an image</li>
            <li>{ajax#&lt;url&gt;}</li>
            <li>Replaced with the result of a cross-origin ajax request</li>
            <li>{ajax#&lt;eval&gt;#&lt;url&gt;}</li>
            <li>Allows parsing the ajax request using <span style="font-family: monospace">$</span> as the placeholder for the result<br>Example: <span style="font-family: monospace">$.substring(0,5)</span></li>
          </ul>
        </div>
      </div>
      
      <div class="templates-modal-bottom">
        <button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
        <button data-action="editor-confirm" class="btn btn-positive"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
      </div>
    </div>
  </div>
</div>`;
    
    $(".js-app-content").prepend(html);
    
    let ele = $(".templates-modal-wrap").first();
    
    ele.on("click", "li[data-template]", function(e){
      let template = me.config.templates[$(this).attr("data-template")];
      useTemplate(template.contents, e.shiftKey);
    });
    
    ele.on("click", "li[data-template] i[data-action]", function(e){
      let identifier = $(this).closest("li").attr("data-template");
      
      switch($(this).attr("data-action")){
        case "edit-template":
          let editor = $(".template-editor");
          
          if (editor.hasClass("invisible")){
            toggleEditor();
          }
          
          let template = me.config.templates[identifier];
          $("[name='template-name']", editor).val(template.name);
          $("[name='template-contents']", editor).val(template.contents);
          
          me.editingTemplate = identifier;
          break;<commit_msg>Fix escaping and update modal text in template plugin<commit_after>enabled(){
  let me = this;
  
  // configuration
  
  this.config = {
    templates: {} // identifier: { name, contents }
  };
  
  const configFile = "config.json";
  
  $TDP.checkFileExists(this.$token, configFile).then(exists => {
    if (!exists){
      $TDP.writeFile(this.$token, configFile, JSON.stringify(this.config));
    }
    else{
      $TDP.readFile(this.$token, configFile, true).then(contents => {
        try{
          $.extend(true, this.config, JSON.parse(contents));
        }catch(err){
          // why :(
        }
      }).catch(err => {
        $TD.alert("error", "Problem loading configuration for the template plugin: "+err.message);
      });
    }
  });
  
  this.saveConfig = () => {
    $TDP.writeFile(this.$token, configFile, JSON.stringify(this.config)).catch(err => {
      $TD.alert("error", "Problem saving configuration for the template plugin: "+err.message);
    });
  };
  
  // button
  
  var buttonHTML = '<button class="manage-templates-btn needsclick btn btn-on-blue full-width txt-left margin-b--12 padding-v--9"><i class="icon icon-bookmark"></i><span class="label padding-ls">Manage templates</span></button>';
  
  this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
  TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="js-tweet-type-button">', buttonHTML+'<div class="js-tweet-type-button">');
  
  var dockedComposePanel = $(".js-docked-compose");
  
  if (dockedComposePanel.length){
    dockedComposePanel.find(".js-tweet-type-button").first().before(buttonHTML);
  }
  
  // css
  
  this.css = window.TDPF_createCustomStyle(this);
  this.css.insert(".manage-templates-btn.active { color: #fff; box-shadow: 0 0 2px 3px #50a5e6; outline: 0; }");
  
  this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
  this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
  this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
  this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; text-align: right; }");
  this.css.insert(".templates-modal-bottom button { margin-left: 4px; }");
  
  this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
  this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
  this.css.insert(".template-list li { display: block; width: 100%; padding: 4px 8px; box-sizing: border-box; }");
  this.css.insert(".template-list li[data-template] { cursor: pointer; }");
  this.css.insert(".template-list li[data-template]:hover { background-color: #d8d8d8; }");
  this.css.insert(".template-list li span { white-space: nowrap; }");
  this.css.insert(".template-list li .icon { opacity: 0.6; margin-left: 4px; padding: 3px; }");
  this.css.insert(".template-list li .icon:hover { opacity: 1; }");
  this.css.insert(".template-list li .template-actions { float: right; }");
  
  this.css.insert(".template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
  this.css.insert(".template-editor-form { flex: 1 1 auto; padding: 12px 16px; font-size: 14px; overflow-y: auto; }");
  this.css.insert(".template-editor-form .compose-text-title { margin: 24px 0 9px; }");
  this.css.insert(".template-editor-form .compose-text-title:first-child { margin-top: 0; }");
  this.css.insert(".template-editor-form input, .template-editor-form textarea { color: #111; background-color: #fff; border: none; border-radius: 0; }");
  this.css.insert(".template-editor-form input:focus, .template-editor-form textarea:focus { box-shadow: inset 0 1px 3px rgba(17, 17, 17, 0.1), 0 0 8px rgba(80, 165, 230, 0.6); }");
  this.css.insert(".template-editor-form textarea { height: 146px; font-size: 14px; padding: 10px; resize: none; }");
  this.css.insert(".template-editor-form .template-editor-tips-button { cursor: pointer; }");
  this.css.insert(".template-editor-form .template-editor-tips-button .icon { font-size: 12px; vertical-align: -5%; margin-left: 4px; }");
  this.css.insert(".template-editor-form .template-editor-tips { display: none; }");
  this.css.insert(".template-editor-form .template-editor-tips p { margin: 10px 0; }");
  this.css.insert(".template-editor-form .template-editor-tips p:first-child { margin-top: 0; }");
  this.css.insert(".template-editor-form .template-editor-tips li:nth-child(2n+1) { margin-top: 5px; padding-left: 6px; font-family: monospace; }");
  this.css.insert(".template-editor-form .template-editor-tips li:nth-child(2n) { margin-top: 1px; padding-left: 14px; opacity: 0.66; }");
  
  this.css.insert(".invisible { display: none !important; }");
  
  // template implementation
  
  var readTemplateTokens = (contents, tokenData) => {
    let startIndex = -1;
    let endIndex = -1;
    
    let data = [];
    let tokenNames = Object.keys(tokenData);
    
    for(let currentIndex = 0; currentIndex < contents.length; currentIndex++){
      if (contents[currentIndex] === '\\'){
        contents = contents.substring(0, currentIndex)+contents.substring(currentIndex+1);
        continue;
      }
      else if (contents[currentIndex] !== '{'){
        continue;
      }
      
      startIndex = currentIndex+1;
      
      for(; startIndex < contents.length; startIndex++){
        if (!tokenNames.some(name => contents[startIndex] === name[startIndex-currentIndex-1])){
          break;
        }
      }
      
      endIndex = startIndex;
      
      let token = contents.substring(currentIndex+1, startIndex);
      let replacement = tokenData[token] || "";
      
      let entry = [ token, currentIndex ];
      
      if (contents[endIndex] === '#'){
        ++endIndex;
        
        let bracketCount = 1;
        
        for(; endIndex < contents.length; endIndex++){
          if (contents[endIndex] === '{'){
            ++bracketCount;
          }
          else if (contents[endIndex] === '}'){
            if (--bracketCount === 0){
              entry.push(contents.substring(startIndex+1, endIndex));
              break;
            }
          }
          else if (contents[endIndex] === '#'){
            entry.push(contents.substring(startIndex+1, endIndex));
            startIndex = endIndex;
          }
          else if (contents[endIndex] === '\\'){
            contents = contents.substring(0, endIndex)+contents.substring(endIndex+1);
          }
        }
      }
      else if (contents[endIndex] !== '}'){
        continue;
      }
      
      data.push(entry);
      
      contents = contents.substring(0, currentIndex)+replacement+contents.substring(endIndex+1);
      currentIndex += replacement.length;
    }
    
    return [ contents, data ];
  };
  
  var doAjaxRequest = (url, evaluator) => {
    return new Promise((resolve, reject) => {
      $.get(url, function(data){
        if (evaluator){
          resolve(eval(evaluator.replace(/\$/g, "'"+data.replace(/(["'\\\n\r\u2028\u2029])/g, "\\$1")+"'")));
        }
        else{
          resolve(data);
        }
      }, "text").fail(function(){
        resolve("");
      });
    });
  };
  
  var useTemplate = (contents, append) => {
    let ele = $(".js-compose-text");
    if (ele.length === 0)return;
    
    let value = append ? ele.val()+contents : contents;
    let prevLength = value.length;
    
    let tokens = null;
    
    [value, tokens] = readTemplateTokens(value, {
      "cursor": "",
      "ajax": "(...)"
    });
    
    ele.val(value);
    ele.focus();
    
    ele[0].selectionStart = ele[0].selectionEnd = value.length;
    
    let promises = [];
    let indexOffset = 0;
    
    for(let token of tokens){
      switch(token[0]){
        case "cursor":
          let [, index1, length ] = token;
          ele[0].selectionStart = index1;
          ele[0].selectionEnd = index1+(length | 0 || 0);
          break;
          
        case "ajax":
          let [, index2, evaluator, url ] = token;
          
          if (!url){
            url = evaluator;
            evaluator = null;
          }

          promises.push(doAjaxRequest(url, evaluator).then(result => {
            const placeholderLen = 5; // "(...)".length
            
            let diff = result.length-placeholderLen;
            let realIndex = indexOffset+index2;
            
            let val = ele.val();
            ele.val(val.substring(0, realIndex)+result+val.substring(realIndex+placeholderLen));
            
            indexOffset += diff;
          }));
          
          break;
      }
    }
    
    if (promises.length > 0){
      let selStart = ele[0].selectionStart;
      let selEnd = ele[0].selectionEnd;
      
      ele.prop("disabled", true);
      
      Promise.all(promises).then(() => {
        ele.prop("disabled", false);
        ele.focus();
        
        ele[0].selectionStart = selStart+indexOffset;
        ele[0].selectionEnd = selEnd+indexOffset;
      });
    }
    
    if (!append){
      hideTemplateModal();
    }
  };
  
  // modal dialog
  
  this.editingTemplate = null;
  
  var showTemplateModal = () => {
    $(".manage-templates-btn").addClass("active");
    
    let html = `
<div class="templates-modal-wrap">
  <div class="templates-modal">
    <div class="template-list">
      <ul></ul>
      
      <div class="templates-modal-bottom">
        <button data-action="new-template" class="btn btn-positive"><i class="icon icon-plus icon-small padding-rs"></i><span class="label">New Template</span></button>
      </div>
    </div>

    <div class="template-editor invisible">
      <div class="template-editor-form">
        <div class="compose-text-title">Template Name</div>
        <input name="template-name" type="text">
        
        <div class="compose-text-title">Contents</div>
        <textarea name="template-contents" class="compose-text scroll-v scroll-styled-v scroll-styled-h scroll-alt"></textarea>

        <div class="compose-text-title template-editor-tips-button">Advanced <i class="icon icon-arrow-d"></i></div>
        <div class="template-editor-tips">
          <p>You can use the following tokens. All tokens except for <span style="font-family: monospace">{ajax}</span> can only be used once.</p>
          <ul>
            <li>{cursor}</li>
            <li>Location where the cursor is placed</li>
            <li>{cursor#&lt;selectionlength&gt;}</li>
            <li>Places cursor and selects a set amount of characters</li>
            <li>{ajax#&lt;url&gt;}</li>
            <li>Replaced with the result of a cross-origin ajax request</li>
            <li>{ajax#&lt;eval&gt;#&lt;url&gt;}</li>
            <li>Allows parsing the ajax request using <span style="font-family: monospace">$</span> as the placeholder for the result<br>Example: <span style="font-family: monospace">$.substring(0,5)</span></li>
          </ul>
          <p>To use special characters in the tweet text, escape them with a backslash:
            <br><span style="font-family: monospace">&nbsp; \\{&nbsp; \\}&nbsp; \\#&nbsp; \\\\</span>
          </p>
        </div>
      </div>
      
      <div class="templates-modal-bottom">
        <button data-action="editor-cancel" class="btn"><i class="icon icon-close icon-small padding-rs"></i><span class="label">Cancel</span></button>
        <button data-action="editor-confirm" class="btn btn-positive"><i class="icon icon-check icon-small padding-rs"></i><span class="label">Confirm</span></button>
      </div>
    </div>
  </div>
</div>`;
    
/* TODO possibly implement this later

<li>{paste}</li>
<li>Paste text or an image from clipboard</li>
<li>{paste#text}</li>
<li>Paste only if clipboard has text</li>
<li>{paste#image}</li>
<li>Paste only if clipboard has an image</li>

*/
    
    $(".js-app-content").prepend(html);
    
    let ele = $(".templates-modal-wrap").first();
    
    ele.on("click", "li[data-template]", function(e){
      let template = me.config.templates[$(this).attr("data-template")];
      useTemplate(template.contents, e.shiftKey);
    });
    
    ele.on("click", "li[data-template] i[data-action]", function(e){
      let identifier = $(this).closest("li").attr("data-template");
      
      switch($(this).attr("data-action")){
        case "edit-template":
          let editor = $(".template-editor");
          
          if (editor.hasClass("invisible")){
            toggleEditor();
          }
          
          let template = me.config.templates[identifier];
          $("[name='template-name']", editor).val(template.name);
          $("[name='template-contents']", editor).val(template.contents);
          
          me.editingTemplate = identifier;
          break;
{'fraction_non_alphanumeric': 0.1731185759855078, 'fraction_numerical': 0.015634229905879572, 'mean_word_length': 2.6204733390362134, 'pattern_counts': {'":': 10, '<': 201, '<?xml version=': 0, '>': 226, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1442382', 'n_tokens_mistral': 8249, 'n_tokens_neox': 7902, 'n_words': 1580}