Доступ к атрибутам / методам комментариев программно в Ruby
-
05-10-2019 - |
Вопрос
Есть ли способ программно обращаться к комментариям метода? или атрибут комментарии?
Я хотел бы использовать его как описание для метода в документации, которую я не хочу быть статичным или генерироваться с RDOC или эквивалентным.
Вот пример класса Ruby:
Class MyClass
##
# This method tries over and over until it is tired
def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
puts thing_to_try
go_go_go thing_to_try, tries - 1
end
end
По сути, я хотел бы иметь возможность сделать следующее:
get_comment MyClass.gogogo # => This method tries over and over until it is tired
Решение
Нет, вы не можете этого сделать.
Вся точка зрения комментариев в том, что они нет Часть программы! Если вы хотите строку, которая является частью вашей программы, просто используйте строку вместо этого.
В большинстве реализаций Ruby комментарии уже выброшены в лексера, что означает, что они даже не достигают анализатор, не говоря уже о устный переводчик или компилятор. В то время, когда код запускается, комментарии уже давно прошли ... Фактически, в реализациях, таких как Рубинс или Ярв, который использует компилятор, просто нет способа хранить комментарии в скомпилированном исполняемом исполнении, поэтому, даже если они не были брошены прочь лексера или парсера, там бы еще Нет способа довести их до временной среды.
Итак, в значительной степени ваш единственный шанс состоит в том, чтобы разбирать исход Ruby, чтобы извлечь комментарии. Однако, как я упомянул выше, вы не можете просто взять Любые парсер, потому что большинство извлеченных парсеров бросают комментарии. (Который, опять же, это весь смысл Из комментариев, поэтому нет ничего плохого в парсере, бросающем их подальше.) Есть, однако, рубиновые анализаторы, которые сохраняют комментарии, особенно те, которые используются в таких инструментах, как RDOC или двор.
Двор особенно интересна, потому что он также содержит операционный двигатель, который позволяет поискать и отфильтровать документацию на основе некоторых мощных предикатов, таких как имя класса, имя метода, дворы, версия API, тип подписи и так далее.
Однако, если вы делать В конечном итоге используя RDOC или двор для анализа, то почему бы не использовать их в первую очередь?
Или, как я предложил выше, если вы хотите строки, просто используйте строки:
module MethodAddedHook
private
def method_added(meth)
(@__doc__ ||= {})[meth] = @__last_doc__ if @__last_doc__
@__last_doc__ = nil
super
end
end
class Module
private
prepend MethodAddedHook
def doc(meth=nil, str)
return @__doc__[meth] = str if meth
@__last_doc__ = str
end
def defdoc(meth, doc, &block)
@__doc__[meth] = doc
define_method(meth, &block)
end
end
Это дает нам метод Module#doc
Что мы можем использовать для документирования либо уже существующего метода, вызывая его с именем метода и DOCSTRING, или вы можете использовать его для документирования самого следующего метода, который вы определяете. Это делает это, хранение DOCSTRING во временной переменной экземпляра, а затем определяя method_added
Крюк, который смотрит на эту переменную экземпляра и хранит свой контент в хеш-документации.
Есть также Module#defdoc
Метод, который определяет и документирует метод в одном.
module Kernel
private
def get_doc(klass, meth)
klass.instance_variable_get(:@__doc__)[meth]
end
end
Это ваш Kernel#get_doc
метод, который получает документацию обратно (или nil
Если метод недокументенна).
class MyClass
doc 'This method tries over and over until it is tired'
def go_go_go(thing_to_try, tries = 10)
puts thing_to_try
go_go_go thing_to_try, tries - 1
end
def some_other_meth; end # Oops, I forgot to document it!
# No problem:
doc :some_other_meth, 'Does some other things'
defdoc(:yet_another_method, 'This method also does something') do |a, b, c|
p a, b, c
end
end
Здесь вы видите три различных способа документирования метода.
Ох, и это работает:
require 'test/unit'
class TestDocstrings < Test::Unit::TestCase
def test_that_myclass_gogogo_has_a_docstring
doc = 'This method tries over and over until it is tired'
assert_equal doc, get_doc(MyClass, :go_go_go)
end
def test_that_myclass_some_other_meth_has_a_docstring
doc = 'Does some other things'
assert_equal doc, get_doc(MyClass, :some_other_meth)
end
def test_that_myclass_yet_another_method_has_a_docstring
doc = 'This method also does something'
assert_equal doc, get_doc(MyClass, :yet_another_method)
end
def test_that_undocumented_methods_return_nil
assert_nil get_doc(MyClass, :does_not_exist)
end
end
Примечание: это довольно хаки. Например, нет блокировки, поэтому, если два потока определяют методы для одного и того же класса одновременно, документация может быть прикручена. (Т. Е. Документ можно отнести к неправильному методу или заблудиться.)
я полагаю, что rake
делает по сути то же самое с его desc
метод, и что кодовая база много Лучше проверено, чем это, поэтому, если вы собираетесь использовать его в производстве, я бы украл код Джима вместо моего.
Другие советы
Комментарии (обычно) выброшены лексером и не доступны в таблицах символов, чтобы Ruby при времени выполнения.
Я думаю, что самый близкий, который вы могли бы сделать, это либо
(a) Реализуйте Get_Comment таким образом, чтобы он создал регенцию на лету и ищет исходный файл для матча. Вам нужно будет изменить свой синтаксис, как это ...
get_comment :MyClass, :go_go_go
Вы будете преобразовывать символы на строки, предположим, что исходный файл является MyClass.rb и поиск в нем для матча на шаблоне Comment-Def-Def-Method_NAME.
(b) иметь метод, вызываемый из каждого исходного файла, который создал таблицу глобального комментариев.
Независимо от того, это грязно и больше хлопот, чем стоит.
Между тем, есть «стандартный» драгоценный камень method_source
Это решает некоторые из этих вопросов:
https://github.com/banister/method_source.
Set.instance_method(:merge).comment
Set.instance_method(:merge).source
Это также приходит с недавними рельсами (рельсы> = 5,0) Версии и используется PRY под капотом.