Redmine ultraviolet plugin debug
- redmine_ultraviolet 실패한 디버깅노트
시작은 이랬다
redmine 에서 저장소 보기 > 파일보기 를 누르는 경우. 웹페이지에서....
Internal error
An error occurred on the page you were trying to access.
If you continue to experience problems please contact your Redmine administrator for assistance.
If you are the Redmine administrator, check your log files for details about the error.
Back
위와같은 에러가 나온다. 뭔가 이상해서 redmine의 log를 뒤지기 시작. 본인의 경우는 production.log 파일이었음.
위의 에러에서 refresh 를 하면 아래와 같은 에러가 나옴.
Processing RepositoriesController#entry (for 180.191.6.73 at 2013-05-05 22:00:10) [GET]
Parameters: {"id"=>"yourID", "action"=>"entry", "controller"=>"repositories", "rev"=>"ktour_dev", "path"=>["composer.json"]}
Rendering template within layouts/base
Rendering repositories/entry
ActionView::TemplateError (Output for xhtml in style is not yet implemented) on line #15 of vendor/plugins/redmine_ultraviolet/app/views/common/_file.rhtml:
12: </style>
13:
14: <div class="uv-file">
15: <%= syntax_highlight( filename, Redmine::CodesetUtil.to_utf8_by_setting(content) ) %>
16: </div>
17:
18: <% content_for :header_tags do %>
ultraviolet (1.0.0) lib/uv/render_processor.rb:14:in `load'
ultraviolet (1.0.0) lib/uv.rb:84:in `parse'
vendor/plugins/redmine_ultraviolet/app/views/common/_file.rhtml:15:in `_run_rhtml_vendor47plugins47redmine_ultraviolet47app47views47common47_file46rhtml_locals_content_file_filename_object'
app/views/repositories/entry.html.erb:11
thin (1.3.1) lib/thin/connection.rb:80:in `pre_process'
thin (1.3.1) lib/thin/connection.rb:78:in `catch'
thin (1.3.1) lib/thin/connection.rb:78:in `pre_process'
thin (1.3.1) lib/thin/connection.rb:53:in `process'
thin (1.3.1) lib/thin/connection.rb:38:in `receive_data'
eventmachine (0.12.10) lib/eventmachine.rb:256:in `run_machine'
eventmachine (0.12.10) lib/eventmachine.rb:256:in `run'
thin (1.3.1) lib/thin/backends/base.rb:61:in `start'
thin (1.3.1) lib/thin/server.rb:159:in `start'
thin (1.3.1) lib/thin/controllers/controller.rb:86:in `start'
thin (1.3.1) lib/thin/runner.rb:185:in `send'
thin (1.3.1) lib/thin/runner.rb:185:in `run_command'
thin (1.3.1) lib/thin/runner.rb:151:in `run!'
thin (1.3.1) bin/thin:6
/usr/bin/thin:8:in `load'
/usr/bin/thin:8
Rendering /var/lib/redmine/public/500.html (500 Internal Server Error)
오호라....... 그럼 지금부터 에러의 내용을 따라 관련된 파일들을 살펴보기로 한다
에러처리를 위한 디버깅
저 에러에서 보면 ultraviolet (1.0.0) lib/uv/render_processor.rb:14:in `load' 부분이 있다.
그래서 /usr/lib64/ruby/gems/1.8/gems/ultraviolet-1.0.0/lib/uv/render_processor.rb 파일을 살펴보기로 했다.
그랬더니 13번째 줄에
renderer = File.join( Uv.render_path, output,"#{style}.render")
raise( ArgumentError, "Output for #{output} in #{style} style is not yet implemented" ) unless File.exists?(renderer)
위와같은 부분이 있다.
에러메세지를 다시 살펴보도록 한다.
Output for xhtml in style is not yet implemented
위의와같은 부분을 분명히 확인할 수 있다. 아마도 #{style} 에 해당하는 부분이 안넘어가는듯?[1]
이제 다시 redmine 안의 ~/vendor/plugins/redmine_ultraviolet/app/views/common/_file.rhtml 파일을 살펴보자.
<style>
.uv-file pre {
font-size: 12px;
margin: 0 0 0 0;
padding: none;
}
.uv-file {
overflow-x: auto;
border: 4px solid black;
}
</style>
<div class="uv-file">
<%= syntax_highlight( filename, Redmine::CodesetUtil.to_utf8_by_setting(content) ) %>
</div>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "uv_themes/#{@uv_theme_name}", :plugin => 'redmine_ultraviolet' %>
<% end %>
내용은 이렇게 된다... 어? style을 받기는 받는데 뭔가 좀 동작이 이상한 느낌이 든다.
다시 ~/vendor/plugins/redmine_ultraviolet/lib/ultraviolet_syntax_patch.rb 파일을 살펴보자. 100번 줄에 이런게 있음
# Usage: Uv.parse(text, output="xhtml", syntax_name=nil, line_numbers=false, render_style="classic", headers=false)
return Uv.parse(content, "xhtml", syntax_name, true, @uv_theme_name)
어.. 뭔가 이상하다... 아무래도 render_processor.rb 파일의 self.load 관련된 부분인거같은데.. 저 syntax_name 을 넘기는 부분이 애매한 느낌이 든다.
관련된 용법을 찾아보니.... 이거이거...
위의 링크부분을 살펴보면 다음과같은 용법이 있다.
result = Uv.parse( text, "xhtml", "ruby", true, "amy")
어? 파일명을 기준으로 뭔가를 넘기는데 아마도 지금의 에러는 "ruby" 에 해당되는 부분이 똑바로 안넘어가서 생기는 문제인거같다.
급한대로 문제해결
정신차리고
- ~/vendor/plugins/redmine_ultraviolet/lib/ultraviolet_syntax_patch.rb
위의 파일을 다시 보면 이런 내용이 있다.
def syntax_highlight_with_uv_syntax_highlight(name, content)
## See: http://ultraviolet.rubyforge.org/svn/lib/uv.rb
## See: http://ultraviolet.rubyforge.org/themes.xhtml
## User selection of UV Theme
selected_theme = User.current.custom_value_for(CustomField.first(:conditions => {:name => 'Ultraviolet Theme'}))
@uv_theme_name = selected_theme || Uv::DEFAULT_THEME
syntaxes = Uv.syntax_for_file(name, content)
if syntaxes.empty?
syntax_name = "plain_text"
else
syntax_name = syntaxes.first.first
end
# Usage: Uv.parse(text, output="xhtml", syntax_name=nil, line_numbers=false, render_style="classic", headers=false)
return Uv.parse(content, "xhtml", syntax_name, true, @uv_theme_name)
end
오호라... syntax_name 이라는 변수에 값이 똑바로 안들어가는듯 하다.. 그럼 이걸 강제로 plain_text 로 지정해주면 어떨까?
...........
오호 안되는군요..(젠장)
어라.. 소스를 잘 보니깐.... @uv_theme_name 이 부분이 문제인거같네요.
ultraviolet 이 설치된곳을 찾아보니
- /usr/lib64/ruby/gems/1.8/gems/ultraviolet-1.0.0/render/xhtml/
이런 디렉토리가 있네요. 이중에서 cobalt 라는게 일단 눈에 띄는거같으니 다음과같이 세팅해보도록 하자
return Uv.parse(content, "xhtml", syntax_name, true, "cobalt")
이번에는 될려나....
에러메세지가 바꼈군. 바뀐 에러메시지는 다음과 같다.
ActionView::TemplateError (No syntax found for plain_text) on line #15 of vendor/plugins/redmine_ultraviolet/app/views/common/_file.rhtml:
12: </style>
13:
14: <div class="uv-file">
15: <%= syntax_highlight( filename, Redmine::CodesetUtil.to_utf8_by_setting(content) ) %>
16: </div>
17:
18: <% content_for :header_tags do %>
ultraviolet (1.0.0) lib/uv.rb:28:in `syntax_node_for'
ultraviolet (1.0.0) lib/uv.rb:85:in `parse'
ultraviolet (1.0.0) lib/uv/render_processor.rb:17:in `load'
ultraviolet (1.0.0) lib/uv.rb:84:in `parse'
웅? 이번에는 plain_text 라는 강조문법을 못찾았다고 나오는군.
그래 누가 이기나 해보자....
이번에는 에러가 나는 위치가 바꼈습니다?
- /usr/lib64/ruby/gems/1.8/gems/ultraviolet-1.0.0/lib/uv.rb
위 파일의 28번째 줄을 보도록 하겠음.
@syntaxes[syntax] = Textpow.syntax(syntax) || raise(ArgumentError, "No syntax found for #{syntax}")
오호 어떤 문법인지에 대한 판단은 Textpow 라는놈이 하는거같네.... gems를 뒤지고 뒤지다보면 다음과같은 디렉토리를 찾을 수 있지. (아싸)
- /usr/lib64/ruby/gems/1.8/gems/textpow-1.3.0/lib/textpow/syntax
위의 디렉토리의 파일들을 대략 보면 말이죠....
파일의 type을 판명할 수 없는 경우에는 강제로 plain 이라는걸 쓰면 될거같은 느낌이 드네?
~/vendor/plugins/redmine_ultraviolet/lib/ultraviolet_syntax_patch.rb
위의 파일내에서 다음의 부분을 살펴봅시다?
if syntaxes.empty?
syntax_name = "plain_text"
else
syntax_name = syntaxes.first.first
end
느낌 오네요. 조아쓰 plain_text 부분을 plain 으로 한번 바꿔볼까요?
어.......
잘된다...? (뭐 일단 에러가 안난다는 정도지만....)
여기서 삽질을 일단락 할까 했으나........
하다가만 추가삽질
파일의 확장자에 따른 부분이 제대로 반환이 안되는듯 하다. 그럼 이제부터 변수를 슬슬 디버깅해보도록 한다.
- ~/vendor/plugins/redmine_ultraviolet/lib/ultraviolet_syntax_patch.rb
문제가 되는 부분은 위의 파일에서
syntaxes = Uv.syntax_for_file(name, content)
이 return 값이 제대로 안되는게 문제. 이제부터 예제를 만들어서 Uv.syntax_for_file 이 어떤식으로 동작하는지를 살펴보도록 한다.
thin/redmine 의 plugin 내에서는 제대로 log가 찍히지 않는다. (10번 refresh 하면 3번정도 log에 찍히니 원....)
일단 위의 메서드 사용에서 name은 파일이름, content 는 파일의 내용으로 보면 되겠다.
웬지는 모르겠지만 puts 로 변수를 찍으면 thin의 log에 간헐적 으로만 나온다.
(이제부터 루비를 눈꼽만큼 알아야 하는 필요가 있음)
- sample을 만들고
- method 의 return 값이 어떤 타입인지를 확인한다음
- return 값을 출력해보고 함수의 동작을 이해한다
일단 UltraViolet 의 /usr/lib64/ruby/gems/1.8/gems/ultraviolet-1.0.0/lib/uv.rb 파일을 살펴본다.
그리고 테스트용 루비프로그램을 짜서 해보는데.......어라 뭔가 이상하다.
def self.syntax_for_file(file_name)
메서드 선언부분을 보면..... 인수가 하나네? (사실 이때 이미 알았어야 했다)
이후 이런저런 디버깅을 해봤는데 기본적인걸 잊고있었다. 다시한번 아래의 파일을 전부 살펴본다.
- ~/vendor/plugins/redmine_ultraviolet/lib/ultraviolet_syntax_patch.rb
require_dependency 'application_helper'
#
# Monkeypatches for the Ultraviolet (Uv) module:
# * Allow Uv.syntax_for_file to handle blobs of content (without existing files)
# * Add THEMES and DEFAULT_THEME
#
module Uv
DEFAULT_THEME = "pastels_on_dark"
THEMES = %w[
active4d
all_hallows_eve
amy
blackboard
brilliance_black
brilliance_dull
cobalt
dawn
eiffel
espresso_libre
idle
iplastic
lazy
mac_classic
magicwb_amiga
pastels_on_dark
slush_poppies
spacecadet
sunburst
twilight
zenburnesque
]
def Uv.syntax_for_file file_name, content=nil
init_syntaxes unless @syntaxes
f = content ? StringIO.new(content) : open(file_name)
first_line = f.find { |line| line.strip.size > 0 } # first non-empty line
f.close
result = []
@syntaxes.each do |key, value|
assigned = false
if value.fileTypes
value.fileTypes.each do |t|
if t == File.basename( file_name ) || t == File.extname( file_name )[1..-1]
result << [key, value]
assigned = true
break
end
end
end
unless assigned
if value.firstLineMatch && value.firstLineMatch =~ first_line
result << [key, value]
end
end
end
result
end
end
#
# UV Syntax highlighting for Redmine
#
module UltravioletSyntaxPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :syntax_highlight, :uv_syntax_highlight
end
end
module InstanceMethods
def syntax_highlight_with_uv_syntax_highlight(name, content)
## See: http://ultraviolet.rubyforge.org/svn/lib/uv.rb
## See: http://ultraviolet.rubyforge.org/themes.xhtml
## User selection of UV Theme
selected_theme = User.current.custom_value_for(CustomField.first(:conditions => {:name => 'Ultraviolet Theme'}))
@uv_theme_name = selected_theme || Uv::DEFAULT_THEME
syntaxes = Uv.syntax_for_file(name, content)
if syntaxes.empty?
syntax_name = "plain"
else
syntax_name = syntaxes.first.first
end
puts "name is #{name} \n";
#puts "content is #{content} \n";
puts "syntaxes is #{syntaxes.first.first} \n";
# Usage: Uv.parse(text, output="xhtml", syntax_name=nil, line_numbers=false, render_style="classic", headers=false)
# return Uv.parse(content, "xhtml", syntax_name, true, @uv_theme_name)
return Uv.parse(content, "xhtml", syntax_name, true, "cobalt")
end
end
end
ApplicationHelper.send(:include, UltravioletSyntaxPatch)
내용은 좀 길지만... 사실 중요한건 다른게 아니라 다음의 한줄이다.
def Uv.syntax_for_file file_name, content=nil
응? 어이.. 이봐요... 새로 class 를 선언해버린겁니까...?...... 인수의 개수가 틀리네?
자 이제 이 class의 이름을 살짝 바꿔서 파일을 로딩한다음 메서드를 실제로 작동시키는 Sample 을 다시 만들어서 내용을 테스트해보겠다.
(이제부터는 간단히 아는 수준을 좀 넘게되는거같네?)
일단 간단한 파일 입출력 예제를 만들어서 원래 plugin 에서 사용하던 contents 에 해당하는 변수를 만들어보도록 한다.
file_name = "파일이름"
# aFile = File.new("filename", "mode")
aFile = File.new(file_name, "r")
temp_string = ""
if aFile
aFile.each do | ch |
temp_string = "#{temp_string}" + "#{ch}"
end
else
puts 'Unable open file'
end
aFile.close
오호.. 파일의 이름을 읽어들여 화면상에 puts 로 표시하는게 잘된다.
사실.. 그래서 ~/vendor/plugins/redmine_ultraviolet/lib/ultraviolet_syntax_patch.rb 파일에 있는 메서드 선언 부분만 새로 복사해서...
예제 파일을 만들어봤으나.. 결국은 아래와 같은 메세지가 나오며 실패되었다.
# ruby ./sample.rb
./sample.rb:31:in `syntax_for_file': undefined local variable or method `init_syntaxes' for Uv:Module (NameError)
from ./sample.rb:82
에이........... 이제는 모르겠다......
뭔가 루비 문법을 더 알면 될거같은데... 나는 여기서 그만 삽질을 접기로 했다.
아래쪽에 삽질한 예제프로그램의 전문을 올린다.
#!/usr/bin/ruby
module Uv
DEFAULT_THEME = "pastels_on_dark"
THEMES = %w[
active4d
all_hallows_eve
amy
blackboard
brilliance_black
brilliance_dull
cobalt
dawn
eiffel
espresso_libre
idle
iplastic
lazy
mac_classic
magicwb_amiga
pastels_on_dark
slush_poppies
spacecadet
sunburst
twilight
zenburnesque
]
def Uv.syntax_for_file file_name, content=nil
init_syntaxes unless @syntaxes
f = content ? StringIO.new(content) : open(file_name)
first_line = f.find { |line| line.strip.size > 0 } # first non-empty line
f.close
result = []
@syntaxes.each do |key, value|
assigned = false
if value.fileTypes
value.fileTypes.each do |t|
if t == File.basename( file_name ) || t == File.extname( file_name )[1..-1]
result << [key, value]
assigned = true
break
end
end
end
unless assigned
if value.firstLineMatch && value.firstLineMatch =~ first_line
result << [key, value]
end
end
end
result
end
end
file_name = "/root/test/sample.php"
# aFile = File.new("filename", "mode")
aFile = File.new(file_name, "r")
temp_string = ""
if aFile
aFile.each do | ch |
temp_string = "#{temp_string}" + "#{ch}"
end
else
puts 'Unable open file'
end
aFile.close
syntaxes = Uv.syntax_for_file(file_name, temp_string)
puts syntaxes.class
참고사항
- 예제를 Plugin 파일과 같은 구조도 만들었는데도 안되는건 예제에 있는 뭔가가 안되는거같다
- 사실.. Uv 말고 그냥 textpow를 써도 될거같다. 그런데...... 거기까지 손대기에는 정신이 혼미해서 포기
- 또 하나 주의할게 redmine_ultraviolet 플러그인이 redmine 1.3 에 맞춰져 있어서 그런거같기도 하다. (증상을 보면 걍 문제인거같지만...)
참고문서
- http://www.ruby-lang.org/ko/documentation/
- http://synch3d.com/wiki/moin/moin.cgi/_c7_c1_b7_ce_b1_d7_b7_a1_b9_d6_20_b7_e7_ba_f1#line305
- http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Ruby
Notes
- ↑ Ruby 에서는 문자열(String) 을 ' ' 또는 " " 으로 표현을 하는데 이 " " 안쪽에 변수를 집어넣으려면 변수를 #{} 문법 안에 넣으면 된다. "abcd #{변수명}" 같은 사용법이라고 보면 된다. 마치 shell script 처럼 동작한다고 생각하면 된다.