lecter 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.all-contributorsrc +26 -0
  3. data/.circleci/config.yml +86 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +16 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +25 -0
  9. data/CHANGELOG.md +20 -0
  10. data/CONTRIBUTING.md +35 -0
  11. data/Gemfile +3 -2
  12. data/Gemfile.lock +37 -69
  13. data/LICENSE.txt +1 -1
  14. data/README.md +162 -54
  15. data/Rakefile +5 -3
  16. data/app/controllers/lecter/diagnosis_controller.rb +28 -49
  17. data/app/views/layouts/lecter.html.erb +258 -0
  18. data/app/views/lecter/diagnosis/new.html.erb +61 -0
  19. data/app/views/lecter/diagnosis/show.html.erb +54 -0
  20. data/bin/console +1 -0
  21. data/bin/rails +2 -0
  22. data/config/locales/en.yml +4 -3
  23. data/config/locales/ru.yml +4 -3
  24. data/config/routes.rb +5 -1
  25. data/lecter.gemspec +9 -5
  26. data/lib/lecter/engine.rb +2 -0
  27. data/lib/lecter/formatter_headers.rb +26 -0
  28. data/lib/lecter/formatter_payload.rb +35 -0
  29. data/lib/lecter/html_generator.rb +66 -0
  30. data/lib/lecter/html_row.rb +42 -0
  31. data/lib/lecter/rack.rb +30 -36
  32. data/lib/lecter/railtie.rb +9 -0
  33. data/lib/lecter/requester.rb +63 -0
  34. data/lib/lecter/trace_point.rb +30 -0
  35. data/lib/lecter/version.rb +3 -1
  36. data/lib/lecter.rb +15 -11
  37. metadata +45 -19
  38. data/.travis.yml +0 -7
  39. data/app/assets/javascripts/lecter.js +0 -2
  40. data/app/assets/stylesheets/lecter.css +0 -261
  41. data/app/views/layouts/lecter.slim +0 -14
  42. data/app/views/lecter/diagnosis/new.erb +0 -20
  43. data/app/views/lecter/diagnosis/show.slim +0 -52
@@ -0,0 +1,54 @@
1
+ <%= content_for :title, t('.title') %>
2
+
3
+ <div class="container">
4
+ <h2><%= t('.executed_code') %></h2>
5
+ </div>
6
+
7
+ <div class="row py-3">
8
+ <div class="col-3 order-2" style="width: 280px;">
9
+ <div class="sticky-top">
10
+ <ul>
11
+ <% @file_listings.each_with_index do |file_listing, index| %>
12
+ <li>
13
+ <a href="#anchor<%= index %>" class="nav-link link-dark">
14
+ <%= raw(file_listing.file_path.split('/').last) %>
15
+ </a>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="col">
23
+ <div class="accordion" id="accordionExample">
24
+ <% @file_listings.each_with_index do |file_listing, index| %>
25
+ <div class="accordion-item">
26
+ <h2 class="accordion-header" id="headingOne">
27
+ <button class="accordion-button" type="button" data-bs-toggle="collapse"
28
+ data-bs-target="#collapse<%=index%>" aria-expanded="true" aria-controls="collapse<%=index%>"
29
+ id="anchor<%=index%>"
30
+ >
31
+ <%= file_listing.file_path.split('/').last %>
32
+ </button>
33
+ </h2>
34
+ <div id="collapse<%=index%>" class="accordion-collapse collapse show" aria-labelledby="heading<%=index%>>" data-bs-parent="#accordionExample">
35
+ <div class="accordion-body">
36
+ <pre>
37
+ <code><% file_listing.html_rows.each do |html_row| %><%= raw(html_row) %><% end %></code>
38
+ </pre>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <script>
48
+ /*! highlight.js v9.15.8 | BSD3 License | git.io/hljslicense */
49
+ !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(a){var f=[],u=Object.keys,N={},c={},n=/^(no-?highlight|plain|text)$/i,s=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",relevance:"r",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},b="</span>",h={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};function _(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function E(e){return e.nodeName.toLowerCase()}function v(e,n){var t=e&&e.exec(n);return t&&0===t.index}function l(e){return n.test(e)}function g(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function R(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),E(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function i(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(i)}}function m(o){function s(e){return e&&e.source||e}function c(e,n){return new RegExp(s(e),"m"+(o.cI?"i":"")+(n?"g":""))}!function n(t,e){if(!t.compiled){if(t.compiled=!0,t.k=t.k||t.bK,t.k){function r(t,e){o.cI&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n=e.split("|");a[n[0]]=[t,n[1]?Number(n[1]):1]})}var a={};"string"==typeof t.k?r("keyword",t.k):u(t.k).forEach(function(e){r(e,t.k[e])}),t.k=a}t.lR=c(t.l||/\w+/,!0),e&&(t.bK&&(t.b="\\b("+t.bK.split(" ").join("|")+")\\b"),t.b||(t.b=/\B|\b/),t.bR=c(t.b),t.endSameAsBegin&&(t.e=t.b),t.e||t.eW||(t.e=/\B|\b/),t.e&&(t.eR=c(t.e)),t.tE=s(t.e)||"",t.eW&&e.tE&&(t.tE+=(t.e?"|":"")+e.tE)),t.i&&(t.iR=c(t.i)),null==t.r&&(t.r=1),t.c||(t.c=[]),t.c=Array.prototype.concat.apply([],t.c.map(function(e){return function(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return g(n,{v:null},e)})),n.cached_variants||n.eW&&[g(n)]||[n]}("self"===e?t:e)})),t.c.forEach(function(e){n(e,t)}),t.starts&&n(t.starts,e);var i=t.c.map(function(e){return e.bK?"\\.?(?:"+e.b+")\\.?":e.b}).concat([t.tE,t.i]).map(s).filter(Boolean);t.t=i.length?c(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i<e.length;i++){var o=r,c=s(e[i]);for(0<i&&(a+=n);0<c.length;){var u=t.exec(c);if(null==u){a+=c;break}a+=c.substring(0,u.index),c=c.substring(u.index+u[0].length),"\\"==u[0][0]&&u[1]?a+="\\"+String(Number(u[1])+o):(a+=u[0],"("==u[0]&&r++)}}return a}(i,"|"),!0):{exec:function(){return null}}}}(o)}function C(e,n,i,t){function c(e,n,t,r){var a='<span class="'+(r?"":h.classPrefix);return e?(a+=e+'">')+n+(t?"":b):n}function o(){E+=null!=l.sL?function(){var e="string"==typeof l.sL;if(e&&!N[l.sL])return _(g);var n=e?C(l.sL,g,!0,f[l.sL]):O(g,l.sL.length?l.sL:void 0);return 0<l.r&&(R+=n.r),e&&(f[l.sL]=n.top),c(n.language,n.value,!1,!0)}():function(){var e,n,t,r,a,i,o;if(!l.k)return _(g);for(r="",n=0,l.lR.lastIndex=0,t=l.lR.exec(g);t;)r+=_(g.substring(n,t.index)),a=l,i=t,void 0,o=s.cI?i[0].toLowerCase():i[0],(e=a.k.hasOwnProperty(o)&&a.k[o])?(R+=e[1],r+=c(e[0],_(t[0]))):r+=_(t[0]),n=l.lR.lastIndex,t=l.lR.exec(g);return r+_(g.substr(n))}(),g=""}function u(e){E+=e.cN?c(e.cN,"",!0):"",l=Object.create(e,{parent:{value:l}})}function r(e,n){if(g+=e,null==n)return o(),0;var t=function(e,n){var t,r,a;for(t=0,r=n.c.length;t<r;t++)if(v(n.c[t].bR,e))return n.c[t].endSameAsBegin&&(n.c[t].eR=(a=n.c[t].bR.exec(e)[0],new RegExp(a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m"))),n.c[t]}(n,l);if(t)return t.skip?g+=n:(t.eB&&(g+=n),o(),t.rB||t.eB||(g=n)),u(t),t.rB?0:n.length;var r=function e(n,t){if(v(n.eR,t)){for(;n.endsParent&&n.parent;)n=n.parent;return n}if(n.eW)return e(n.parent,t)}(l,n);if(r){var a=l;for(a.skip?g+=n:(a.rE||a.eE||(g+=n),o(),a.eE&&(g=n));l.cN&&(E+=b),l.skip||l.sL||(R+=l.r),(l=l.parent)!==r.parent;);return r.starts&&(r.endSameAsBegin&&(r.starts.eR=r.eR),u(r.starts)),a.rE?0:n.length}if(function(e,n){return!i&&v(n.iR,e)}(n,l))throw new Error('Illegal lexeme "'+n+'" for mode "'+(l.cN||"<unnamed>")+'"');return g+=n,n.length||1}var s=B(e);if(!s)throw new Error('Unknown language: "'+e+'"');m(s);var a,l=t||s,f={},E="";for(a=l;a!==s;a=a.parent)a.cN&&(E=c(a.cN,"",!0)+E);var g="",R=0;try{for(var d,p,M=0;l.t.lastIndex=M,d=l.t.exec(n);)p=r(n.substring(M,d.index),d[0]),M=d.index+p;for(r(n.substr(M)),a=l;a.parent;a=a.parent)a.cN&&(E+=b);return{r:R,value:E,language:e,top:l}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{r:0,value:_(n)};throw e}}function O(t,e){e=e||h.languages||u(N);var r={r:0,value:_(t)},a=r;return e.filter(B).filter(M).forEach(function(e){var n=C(e,t,!1);n.language=e,n.r>a.r&&(a=n),n.r>r.r&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function d(e){return h.tabReplace||h.useBR?e.replace(t,function(e,n){return h.useBR&&"\n"===e?"<br>":h.tabReplace?n.replace(/\t/g,h.tabReplace):""}):e}function o(e){var n,t,r,a,i,o=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=s.exec(i))return B(t[1])?t[1]:"no-highlight";for(n=0,r=(i=i.split(/\s+/)).length;n<r;n++)if(l(a=i[n])||B(a))return a}(e);l(o)||(h.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n"):n=e,i=n.textContent,r=o?C(o,i,!0):O(i),(t=R(n)).length&&((a=document.createElementNS("http://www.w3.org/1999/xhtml","div")).innerHTML=r.value,r.value=function(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset<n[0].offset?e:n:"start"===n[0].event?e:n:e.length?e:n}function c(e){a+="<"+E(e)+f.map.call(e.attributes,function(e){return" "+e.nodeName+'="'+_(e.value).replace('"',"&quot;")+'"'}).join("")+">"}function u(e){a+="</"+E(e)+">"}function s(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var l=o();if(a+=_(t.substring(r,l[0].offset)),r=l[0].offset,l===e){for(i.reverse().forEach(u);s(l.splice(0,1)[0]),(l=o())===e&&l.length&&l[0].offset===r;);i.reverse().forEach(c)}else"start"===l[0].event?i.push(l[0].node):i.pop(),s(l.splice(0,1)[0])}return a+_(t.substr(r))}(t,R(a),i)),r.value=d(r.value),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?c[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function p(){if(!p.called){p.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,o)}}function B(e){return e=(e||"").toLowerCase(),N[e]||N[c[e]]}function M(e){var n=B(e);return n&&!n.disableAutodetect}return a.highlight=C,a.highlightAuto=O,a.fixMarkup=d,a.highlightBlock=o,a.configure=function(e){h=g(h,e)},a.initHighlighting=p,a.initHighlightingOnLoad=function(){addEventListener("DOMContentLoaded",p,!1),addEventListener("load",p,!1)},a.registerLanguage=function(n,e){var t=N[n]=e(a);i(t),t.aliases&&t.aliases.forEach(function(e){c[e]=n})},a.listLanguages=function(){return u(N)},a.getLanguage=B,a.autoDetection=M,a.inherit=g,a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",r:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,r:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,r:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,r:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,r:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,r:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,r:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,r:0},a});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d;var l=[{b:/^\s*=>/,starts:{e:"$",c:i.c=d}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(l).concat(d)}});
50
+
51
+ $(document).ready(function(){
52
+ hljs.initHighlighting();
53
+ })
54
+ </script>
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'lecter'
data/bin/rails CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  # This command will automatically be run when you run "rails" with Rails gems
3
5
  # installed from the root of your application.
4
6
 
@@ -6,10 +6,11 @@ en:
6
6
  diagnosis:
7
7
  show:
8
8
  title: 'Diagnosis results'
9
- response_status: 'Response status: %{status}'
9
+ executed_code: 'Executed code:'
10
10
  new:
11
11
  title: 'New diagnosis'
12
12
  endpoint: 'Endpoint'
13
- params: 'Parameters'
13
+ body_raw: 'Body (raw)'
14
14
  method: 'HTTP method'
15
- create: 'Create'
15
+ send: 'Send request'
16
+ headers: 'Headers'
@@ -6,10 +6,11 @@ ru:
6
6
  diagnosis:
7
7
  show:
8
8
  title: 'Диагноз'
9
- response_status: 'Статус ответа: %{status}'
9
+ executed_code: 'Используемый код:'
10
10
  new:
11
11
  title: 'Новый диагноз'
12
12
  endpoint: 'Абсолютный путь'
13
- params: 'Параметры'
13
+ body_raw: 'Тело запроса'
14
14
  method: 'HTTP метод'
15
- create: 'Создать'
15
+ send: 'Послать запрос'
16
+ headers: 'Заголовки'
data/config/routes.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Lecter::Engine.routes.draw do
2
- resource :diagnosis, only: %i[show create new], controller: :diagnosis
4
+ root 'diagnosis#new'
5
+
6
+ resource :diagnosis, only: %w[new create show], controller: :diagnosis
3
7
  end
data/lecter.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'lecter/version'
@@ -8,8 +10,9 @@ Gem::Specification.new do |spec|
8
10
  spec.authors = ['Neodelf']
9
11
  spec.email = ['neodelf@gmail.com']
10
12
 
11
- spec.summary = %q{Write a short summary, because RubyGems requires one.}
12
- spec.description = %q{Write a longer description or delete this line.}
13
+ spec.summary = 'Show executable code by request.'
14
+ spec.description = 'The main purpose of that gem is helping developers to understand which '\
15
+ 'code executes by request.<br>'
13
16
  spec.homepage = 'https://github.com/neodelf/lecter'
14
17
  spec.license = 'MIT'
15
18
 
@@ -21,7 +24,7 @@ Gem::Specification.new do |spec|
21
24
 
22
25
  # Specify which files should be added to the gem when it is released.
23
26
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
27
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
28
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
29
  end
27
30
  spec.bindir = 'exe'
@@ -29,9 +32,10 @@ Gem::Specification.new do |spec|
29
32
  spec.require_paths = ['lib']
30
33
 
31
34
  spec.add_development_dependency 'bundler', '~> 2.0'
32
- spec.add_development_dependency 'rake', '~> 10.0'
35
+ spec.add_development_dependency 'rake', '~> 13.0'
33
36
  spec.add_development_dependency 'rspec', '~> 3.0'
37
+ spec.add_development_dependency 'rubocop'
34
38
 
35
- spec.add_runtime_dependency 'slim-rails'
36
39
  spec.add_runtime_dependency 'rest-client'
40
+ spec.add_runtime_dependency 'simplecov', '0.17.1'
37
41
  end
data/lib/lecter/engine.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Lecter
2
4
  class Engine < ::Rails::Engine
3
5
  isolate_namespace Lecter
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lecter
4
+ class FormatterHeaders
5
+ WRONG_HEADERS_MSG = 'Wrong headers'
6
+ attr_reader :result, :error_message
7
+
8
+ def initialize(headers)
9
+ @dirty_headers = headers
10
+ end
11
+
12
+ def call
13
+ @result = dirty_headers
14
+ .split(',')
15
+ .map { |header_with_value| header_with_value.split('=') }
16
+ .to_h
17
+ rescue StandardError
18
+ @error_message = WRONG_HEADERS_MSG
19
+ false
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :dirty_headers
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Lecter
6
+ class FormatterPayload
7
+ WRONG_PARAMETERS_MSG = 'Wrong parameters'
8
+ attr_reader :result, :error_message
9
+
10
+ def initialize(payload)
11
+ @dirty_payload = payload
12
+ end
13
+
14
+ def call
15
+ @result = json_parse(dirty_payload).merge(lecter_enabled_parameter)
16
+ rescue JSON::ParserError
17
+ @error_message = WRONG_PARAMETERS_MSG
18
+ false
19
+ end
20
+
21
+ private
22
+
23
+ attr_accessor :dirty_payload
24
+
25
+ def json_parse(string)
26
+ string = '{' + string + '}' unless string.match(/\A{.*}\z/)
27
+ string.gsub!('=>', ':')&.gsub!(/(“|”)/, '"')
28
+ JSON.parse(string)
29
+ end
30
+
31
+ def lecter_enabled_parameter
32
+ { lecter_enabled: true }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lecter
4
+ class HtmlGenerator
5
+ COUNT_LINES_AROUND_RUNNING_ROW = 5
6
+ ELLIPSIS = '...'
7
+ NEW_LINE = "\n"
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+
13
+ def call
14
+ @data.each.map do |item|
15
+ @file_path = item.keys.first
16
+ @executable_row_numbers = item.values.flatten
17
+ previous_row_is_empty = false
18
+
19
+ html_rows = file_context.each_with_index.map do |file_row, file_row_index|
20
+ @file_row_index = file_row_index
21
+ row_executable = executable_row_numbers.include?(file_row_index + 1)
22
+
23
+ if row_executable || file_row_in_showing_range?(file_row_index)
24
+ previous_row_is_empty = false
25
+ Lecter::HtmlRow.new(
26
+ file_row,
27
+ file_row_index + 1,
28
+ row_executable,
29
+ executable_row_numbers
30
+ ).create
31
+ elsif !previous_row_is_empty
32
+ previous_row_is_empty = true
33
+ ELLIPSIS + NEW_LINE
34
+ end
35
+ end
36
+
37
+ FileListing.new(file_path, html_rows)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ attr_accessor :executable_row_numbers, :file_row_index, :file_path
44
+
45
+ def file_row_in_showing_range?(_index)
46
+ executable_row_numbers.reduce(false) do |memo, row_number|
47
+ memo ||
48
+ (row_number - COUNT_LINES_AROUND_RUNNING_ROW - 1..
49
+ row_number + COUNT_LINES_AROUND_RUNNING_ROW - 1).include?(file_row_index)
50
+ end
51
+ end
52
+
53
+ def file_context
54
+ File.open(file_path, 'r').read.split(NEW_LINE)
55
+ end
56
+ end
57
+
58
+ class FileListing
59
+ attr_reader :file_path, :html_rows
60
+
61
+ def initialize(file_path, html_rows)
62
+ @file_path = file_path
63
+ @html_rows = html_rows
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lecter
4
+ class HtmlRow
5
+ ARROW = '-> '
6
+ BACKGROUND_INCLUDED_ROW = '#4a4a4a'
7
+ NEW_LINE_SYMBOL = "\n"
8
+
9
+ def initialize(row, row_number, row_executable, order_of_executed_lines)
10
+ @row = row
11
+ @row_number = row_number
12
+ @row_executable = row_executable
13
+ @order_of_executed_lines = order_of_executed_lines
14
+ end
15
+
16
+ def create
17
+ "<div style='#{style}'><code>#{html_row}</code></div>"
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :row, :row_number, :row_executable, :order_of_executed_lines
23
+
24
+ def html_row
25
+ [row_number, row, row_calling_order_number].join(' ') + NEW_LINE_SYMBOL
26
+ end
27
+
28
+ def row_calling_order_number
29
+ return unless row_executable
30
+
31
+ ARROW + order_of_executed_lines
32
+ .each_with_index
33
+ .select { |_, index| order_of_executed_lines[index] == row_number }
34
+ .map { |_, index| index + 1 }
35
+ .join(', ')
36
+ end
37
+
38
+ def style
39
+ row_executable ? "background-color: #{BACKGROUND_INCLUDED_ROW};" : nil
40
+ end
41
+ end
42
+ end
data/lib/lecter/rack.rb CHANGED
@@ -1,45 +1,39 @@
1
- class Lecter::Rack
2
- def initialize(app)
3
- @app = app
4
- end
1
+ # frozen_string_literal: true
5
2
 
6
- def call(env)
7
- request = Rack::Request.new(env)
8
- if request.params['lecter_analysis']
9
- thread = Thread.current
10
- thread[:items] = ''
11
- tp = TracePoint.new(:line, :class, :call, :c_call, :return) do |tp|
12
- if tp.path &&
13
- !tp.path.include?('/app/views') &&
14
- !tp.path.include?('/app/helpers') &&
15
- tp.path.include?(Rails.root.to_s) &&
16
- tp.method_id != :method_added &&
17
- tp.defined_class != Module &&
18
- tp.defined_class != Class &&
19
- tp.defined_class != String &&
20
- tp.defined_class != Kernel &&
21
- tp.defined_class != NilClass
3
+ module Lecter
4
+ class Rack
5
+ def initialize(app)
6
+ @app = app
7
+ @tp = Lecter::TracePoint.new.build
8
+ end
22
9
 
23
- thread[:items] += [tp.path, tp.lineno, tp.defined_class, tp.method_id, tp.event].join(' ') + ';'
24
- end
10
+ def call(env)
11
+ if ::Rack::Request.new(env).params['lecter_enabled']
12
+ thread = Thread.current
13
+ thread[:items] = ''
14
+ tp.enable
15
+ ActionController::Base.allow_forgery_protection = false
25
16
  end
26
- tp.enable
27
- ActionController::Base.allow_forgery_protection = false
28
- end
29
17
 
30
- status, headers, response = @app.call(env)
18
+ status, headers, response = @app.call(env)
31
19
 
32
- if tp
33
- response = [status.to_s + thread[:items]]
34
- status = 200
35
- headers = {}
36
- end
20
+ if tp.enabled?
21
+ response = [status.to_s + thread[:items]]
22
+ status = 200
23
+ headers = {}
24
+ end
37
25
 
38
- [status, headers, response]
39
- ensure
40
- if tp
41
- tp.disable
42
- ActionController::Base.allow_forgery_protection = true
26
+ [status, headers, response]
27
+ ensure
28
+ if tp.enabled?
29
+ tp.disable
30
+ ActionController::Base.allow_forgery_protection = true
31
+ Thread.current[:items] = nil
32
+ end
43
33
  end
34
+
35
+ private
36
+
37
+ attr_reader :tp
44
38
  end
45
39
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lecter
4
+ class LecterRailtie < Rails::Railtie
5
+ initializer 'lecter.configure_rails_initialization' do |app|
6
+ app.middleware.use Lecter::Rack
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lecter
4
+ class Requester
5
+ WRONG_URL_MSG = 'Wrong url'
6
+
7
+ attr_reader :lines, :error_message
8
+
9
+ def initialize(params)
10
+ @method = params[:method]
11
+ @url = params[:url]
12
+ @payload = params[:payload]
13
+ @lines = []
14
+ @headers = params[:headers]
15
+ end
16
+
17
+ def call
18
+ return false unless response
19
+
20
+ prepare_lines
21
+ rescue URI::InvalidURIError
22
+ @error_message = WRONG_URL_MSG
23
+ false
24
+ rescue RestClient::ExceptionWithResponse => e
25
+ @error_message = e.message
26
+ false
27
+ end
28
+
29
+ private
30
+
31
+ attr_accessor :method, :url, :payload, :headers
32
+
33
+ def prepare_lines
34
+ items.each do |item|
35
+ file, line_number = item.split(' ')
36
+ line_number = line_number.to_i
37
+
38
+ if line_belongs_to_last?(file)
39
+ lines.last[file] = lines.last[file] << line_number
40
+ else
41
+ lines << { file.to_s => [line_number] }
42
+ end
43
+ end
44
+ end
45
+
46
+ def response
47
+ @response ||= RestClient::Request.execute(
48
+ method: method,
49
+ url: url,
50
+ payload: payload,
51
+ headers: headers
52
+ )
53
+ end
54
+
55
+ def items
56
+ @items ||= response.body[3..-1].split(';')
57
+ end
58
+
59
+ def line_belongs_to_last?(file)
60
+ lines.last.is_a?(Hash) && lines.last.keys.first.to_s == file
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lecter
4
+ class TracePoint
5
+ def build
6
+ tp = ::TracePoint.new(:line, :class, :call, :c_call, :return) do |trace_point|
7
+ if trace_point.path&.exclude?('/app/views') &&
8
+ trace_point.path&.exclude?('/app/helpers') &&
9
+ trace_point.path&.include?(Rails.root.to_s) &&
10
+ trace_point.method_id != :method_added &&
11
+ trace_point.defined_class != Module &&
12
+ trace_point.defined_class != Class &&
13
+ trace_point.defined_class != String &&
14
+ trace_point.defined_class != Kernel &&
15
+ trace_point.defined_class != NilClass
16
+
17
+ Thread.current[:items] += [
18
+ trace_point.path,
19
+ trace_point.lineno,
20
+ trace_point.defined_class,
21
+ trace_point.method_id,
22
+ trace_point.event
23
+ ].join(' ') + ';'
24
+ end
25
+ end
26
+
27
+ tp
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Lecter
2
- VERSION = '0.1.6'
4
+ VERSION = '0.2.0'
3
5
  end
data/lib/lecter.rb CHANGED
@@ -1,15 +1,19 @@
1
- require 'lecter/version'
2
- require 'lecter/engine'
1
+ # frozen_string_literal: true
2
+
3
+ require 'lecter/engine' if defined?(Rails::Engine)
4
+ require 'lecter/formatter_payload'
5
+ require 'lecter/html_generator'
6
+ require 'lecter/html_row'
3
7
  require 'lecter/rack'
8
+ require 'lecter/railtie' if defined?(Rails::Railtie)
9
+ require 'lecter/requester'
10
+ require 'lecter/version'
11
+ require 'lecter/trace_point'
12
+ require 'lecter/formatter_headers'
4
13
 
5
- module Lecter
6
- # autoload :Rack, 'lecter/rack'
14
+ require 'rest-client'
7
15
 
8
- if defined? Rails::Railtie
9
- class LecterRailtie < Rails::Railtie
10
- initializer 'lecter.configure_rails_initialization' do |app|
11
- app.middleware.use Lecter::Rack
12
- end
13
- end
14
- end
16
+ module Lecter
17
+ AVAILABLE_METHODS = %w[GET POST PUT PATCH DELETE].freeze
18
+ DEFAULT_METHOD = 'GET'
15
19
  end