Redmine ultraviolet plugin debug

From 흡혈양파의 인터넷工房
Jump to navigation Jump to search
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 을 넘기는 부분이 애매한 느낌이 든다.

관련된 용법을 찾아보니.... 이거이거...

http://blog.alno.name/en/2009/02/code-highlighters-ruby

위의 링크부분을 살펴보면 다음과같은 용법이 있다.

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에 간헐적 으로만 나온다.

(이제부터 루비를 눈꼽만큼 알아야 하는 필요가 있음)

  1. sample을 만들고
  2. method 의 return 값이 어떤 타입인지를 확인한다음
  3. 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


참고사항

  1. 예제를 Plugin 파일과 같은 구조도 만들었는데도 안되는건 예제에 있는 뭔가가 안되는거같다
  2. 사실.. Uv 말고 그냥 textpow를 써도 될거같다. 그런데...... 거기까지 손대기에는 정신이 혼미해서 포기
  3. 또 하나 주의할게 redmine_ultraviolet 플러그인이 redmine 1.3 에 맞춰져 있어서 그런거같기도 하다. (증상을 보면 걍 문제인거같지만...)


참고문서


Notes

  1. Ruby 에서는 문자열(String) 을 ' ' 또는 " " 으로 표현을 하는데 이 " " 안쪽에 변수를 집어넣으려면 변수를 #{} 문법 안에 넣으면 된다. "abcd #{변수명}" 같은 사용법이라고 보면 된다. 마치 shell script 처럼 동작한다고 생각하면 된다.