読者です 読者をやめる 読者になる 読者になる

頭の中は異空間

生活を日々ハックしてよりよくするブログ

HTML上に書いたコードをtermnialの様に色分けするロジックをJSで書いてみた

どうも。

 

何らかのコードをhtml上に記載する場合、それがメモ帳みたく単なる文字列になってたらなんか残念ですね。

そこで、すぐ思い当たる点として、有名なgoogle code prettifyを使うといいでしょう。

github.com

 

でも、それだと話が終わってしまいます。

外部ライブラリに頼るのもいいけど、自己流アレンジできるよバージョンをあえて自分で作っちゃうのもあり。つってもあそこまですぐれたものはさすがにできないので、簡易版をサクッと作ってしかもカスタマイズがしやすいものにしちゃいましょう。

 


JS( + jQuery)

・ライブラリ側
$(function() {
  $.fn.codeArrangeMent = function(class_name) {
    var LANGUAGES = ["ruby", "html", "css", "javascript", "perl", "shell script"];
    Object.freeze(LANGUAGES);
    var RESERVED = {
      "ruby": [
        "extend", "class", "return", "and", "or", "raise", "try", "if", "elsif",
        "else", "def", "end", "private", "public", "protected", "new", "for", "foreach",
        "catch", "true", "false", "when", "case", "unless", "do", "while", "self",
        "include", "require", "module", "super", "in"
      ]
    };
    Object.freeze(RESERVED);

    var TextChecker = function() {
      return {
        isMethod: function(prev_text) {
          return prev_text.match(/^def/);
        },
        isClassOrModuleName: function(prev_text) {
          return (prev_text === "class") || (prev_text === "module");
        },
        isSymbol: function(text) {
          return text.match(/^¥:/);
        },
        hasCommentMark: function(text_line) {
          return text_line.match(/#/);
        },
        isConstOrClassName: function(text) {
          return text.match(/^[A-Z]/);
        }
      };
    }();
    var TextModifier = function() {
      return {
        color_context: function(class_name, text) {
          var pink_pallet = {
            "ruby": ["require", "include", "class", "def", "module", "end", "extend"]
          };
          var red_pallet = {
            "ruby": 
            ["true", "false", "self", "nil"]
          };
          var color = (pink_pallet[class_name].indexOf(text) >= 0) ? "pink" : "yellow";
          color = (red_pallet[class_name].indexOf(text) >= 0) ? "red" : color;

          return color;
        },
        mark_eliminated_text: function(text) {
          var marks = "(){}[]<>-+*=|&,._¥/'" + '"';
          var mark = '';

          if(!TextChecker.isSymbol(text)) { marks += ":"; }

          var cut = text;
          for (var i = 0;i < marks.length;i++) {
            if (mark = text.match(new RegExp("¥¥" + marks[i], "g"))) {
              cut = text.replace(mark, "");
              break;
            }
          }
          return [cut, mark];
        }
      };
    }();

    var arrangeCode = function(class_name) {
      var arrange = [];

      var target_code = $("pre." + class_name);
      var text_lines = target_code.text().split(/¥r¥n|¥r|¥n/);
      var lang = RESERVED[class_name];
      var result = '';
      var prev_text = '';
      var comment_index = -1;

      // ruby
      arrange["ruby"] = function() {
        for (var l_index = 0;l_index < text_lines.length;l_index++) {
          comment_index = -1:
          var texts = text_lines[l_index].split(' ');
          if (TextChecker.hasCommentMark(text_lines[l_index])) {
            var comment_index = texts.indexOf('#');
          }
          for (var i = 0;i < texts.length;i++) {
            if (comment_index >= 0 && i >= comment_index) {
              text_lines[l_index] = text_lines[l_index].replace(
                new RegExp(texts[i], "g"), "<span style='color: green;'>" + texts[i] + "</span>"
              );
            }
            var text = texts[i];
            for (var j = 0;j < lang.length;j++) {
              if (text.match(new RegExp('(^| )+' + lang[j], "g"))) {
                var [display_text, mark] = TextModifier.mark_eliminated_text(text);
                text_lines[l_index] = text_lines[l_index].replace(
                  new RegExp(text, "g"), "<span style='color: "
                  + TextModifier.color_context(class_name, text) + ”'>.”
                  + display_text + "'>" + (mark === null ? '' : mark)
                );
              }
            }
            if (text.match(/(^| +)[0-9]+/)) {
              var [display_text, mark] = TextModifier.mark_eliminated_text(text);
              text_lines[l_index] = text_lines[l_index].replace(
                new RegExp(text, "g"), "<span style='color: red;'>"
                + display_text + "</span>" + (mark === null ? '' : mark)
              );
            } else if (i > 0 && TextChecker.isClassOrModuleName(texts[i - 1])) {
              var [display_text, mark] = TextModifier.mark_eliminated_text(text);
              text_lines[l_index] = text_lines[l_index].replace(
                new RegExp(text, "g"), "<span style='color: green;'>"
                + display_text + "</span>" + (mark === null ? '' : mark)
              );
            } else if (TextChecker.isSymbol(text) || TextChecker.isConstOrClassName(text) || (i > 0 && TextChecker.isMethod(texts[i - 1]))) {
              text_lines[l_index] = text_lines[l_index].replace(
                new RegExp(text, "g"), "<span style='color: #90FEFF;'>"
                + display_text + "</span>" + (mark === null ? '' : mark)
              );
            }
            result = text_lines.join('¥r¥n');
          }
        }
        target_code.before($("<pre class='code '" + class_name + "'>" + result + "</pre>"));
        target_code.remove();
      };
      // html
      arrange["html"] = function() {
        // ここにHTML arrange用のコード
      };

      arrange[class_name]();
    };

    // entry point
    if (LANGUAGES.indexOf(class_name) >= 0) {
      arrangeCode(class_name);
    }
  };
});
・呼び出し側
$(function() {
  $(".code").codeArrangement($(".code").attr("class").split(" ")[1]);
});

nilが正確には常に赤字表示でない点や、ブロックがネストした場合、内側のendが黄色になってない点などはまだ修正の余地がありそうです。

red_palletなどの設定値については、自分で後で追加すれば良く、カスタムはそこまで難しくないかと。rubyだけ書きましたが、具体的なコードごとの予約後、色付けは言語ごとに異なって量が半端じゃないので続きを書く気はありませんw

呼び出し側は1行ですみます。ここはライブラリっぽく。

書いてて気づいたんですが、 はてぶにまともにコードを載せようとすると、HTMLコードを含んだ時点で記事自体の表示に影響を及ぼしてしまいます。なので、<は&lt;、>は&gt;などエスケープする必要が出てきます。

 

HTML

<pre class="code ruby">
~~~~ (実際のコード)
</pre>

こちらは好きなコードを書けばいいですが、preのclass属性にcode ~~(言語名)で追加すれば作動します。仕事の空き時間で作ってたのは内緒です...

 

 

 

今回はこの辺で!