├── meta ├── created ├── version ├── name ├── title ├── organization ├── authors ├── summary ├── copyrights ├── repositories ├── requirements ├── resources └── description ├── .document ├── Gemfile ├── Confile.rb ├── Testfile ├── .gitignore ├── .travis.yml ├── Rulefile ├── MANIFEST.txt ├── HISTORY.rdoc ├── test └── case_specification.rb ├── Assembly ├── lib └── rdoc │ ├── discover.rb │ └── generator │ ├── shomen_extensions.rb │ └── shomen.rb ├── README.rdoc ├── LICENSE.txt ├── .ruby └── pkg └── rdoc-shomen.gemspec /meta/created: -------------------------------------------------------------------------------- 1 | 2010-07-01 -------------------------------------------------------------------------------- /meta/version: -------------------------------------------------------------------------------- 1 | 0.1.1 2 | -------------------------------------------------------------------------------- /meta/name: -------------------------------------------------------------------------------- 1 | rdoc-shomen 2 | -------------------------------------------------------------------------------- /meta/title: -------------------------------------------------------------------------------- 1 | RDoc Shomen 2 | -------------------------------------------------------------------------------- /meta/organization: -------------------------------------------------------------------------------- 1 | rubyworks 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/*.rb 2 | lib/**/*.rb 3 | [A-Z]*.* 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec :path=>'pkg' 3 | -------------------------------------------------------------------------------- /meta/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - trans 3 | -------------------------------------------------------------------------------- /meta/summary: -------------------------------------------------------------------------------- 1 | RDoc Generator for Shomen Documentation Format 2 | -------------------------------------------------------------------------------- /meta/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - 2011 Rubyworks (BSD-2-Clause) 3 | 4 | -------------------------------------------------------------------------------- /meta/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/rdoc-shomen.git 3 | -------------------------------------------------------------------------------- /Confile.rb: -------------------------------------------------------------------------------- 1 | 2 | config :rubytest do |run| 3 | run.files < 'test/case_*.rb' 4 | end 5 | 6 | -------------------------------------------------------------------------------- /meta/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - shomen-model 3 | - citron (test) 4 | - detroit (build) 5 | 6 | -------------------------------------------------------------------------------- /Testfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | Test.run :default do |run| 4 | run.files << 'test/case_*.rb' 5 | end 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .fire/digest 2 | .rdoc_options 3 | .yardoc 4 | doc 5 | log 6 | pkg/*.gem 7 | tmp 8 | web 9 | work/sandbox/.yardoc 10 | work/sandbox/.rdoc 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec rubytest" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | - rbx 8 | - rbx-19mode 9 | - jruby 10 | - jruby-19mode 11 | 12 | -------------------------------------------------------------------------------- /Rulefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ignore 'work', '.yardoc', 'doc', 'log', 'pkg', 'tmp', 'web' 4 | 5 | desc "run tests" 6 | task "test" do 7 | cmd = "rubytest" 8 | sh cmd 9 | end 10 | 11 | task "test:cov" do 12 | cmd = "test -p cov" 13 | sh cmd 14 | end 15 | 16 | -------------------------------------------------------------------------------- /meta/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/rdoc-shomen 3 | docs: http://rubydoc.info/gems/rdoc-shoment 4 | code: http://github.com/rubyworks/rdoc-shomen 5 | bugs: http://github.com/rubyworks/rdoc-shomen/issues 6 | mail: http://groups.google.com/groups/rubyworks-mailinglist 7 | 8 | -------------------------------------------------------------------------------- /MANIFEST.txt: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yardopts bin lib spec test [A-Z]*.* 2 | .ruby 3 | lib/rdoc/discover.rb 4 | lib/rdoc/generator/shomen.rb 5 | lib/rdoc/generator/shomen_extensions.rb 6 | test/case_specification.rb 7 | test/fixture/doc/doc.json 8 | HISTORY.rdoc 9 | LICENSE.txt 10 | README.rdoc 11 | Confile.rb 12 | -------------------------------------------------------------------------------- /meta/description: -------------------------------------------------------------------------------- 1 | RDoc-Shomen is an RDoc generator plugin that can be used to generate Shomen 2 | documentation. This is an alternative to the shomen command line tool which 3 | use the `.rdoc` cache to generate a shomen document. In contrast the rdoc-shomen 4 | generator operates as a traditional rdoc plugin. 5 | -------------------------------------------------------------------------------- /HISTORY.rdoc: -------------------------------------------------------------------------------- 1 | = RELEASE HISTORY 2 | 3 | == 0.1.1 | 2012-04-20 4 | 5 | This release simple uses the new shomen-model gem, 6 | instead of the previous shomen gem. 7 | 8 | Changes: 9 | 10 | * Use shomen-model instead of shomen. 11 | 12 | 13 | == 0.1.0 | 2012-04-18 14 | 15 | This is the first release of rdoc-shomen. 16 | 17 | Changes: 18 | 19 | * Happy first release day! 20 | 21 | -------------------------------------------------------------------------------- /test/case_specification.rb: -------------------------------------------------------------------------------- 1 | require 'citron' 2 | require 'brass' 3 | require 'json' 4 | 5 | testcase "Document conforms to specificaiton" do 6 | 7 | # IMPORTANT! For now we need to hand create this document. 8 | FIXTURE_DOCUMENT = 'test/fixture/doc/doc.json' 9 | 10 | def initialize(*a,&b) 11 | super(*a,&b) 12 | 13 | @doc = JSON.load(File.new(FIXTURE_DOCUMENT)) 14 | end 15 | 16 | test "(metadata)" do 17 | assert @doc['(metadata)'] 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | service : Email 4 | mailto : 5 | - ruby-talk@ruby-lang.org 6 | - rubyworks-mailinglist@googlegroups.com 7 | 8 | github: 9 | gh_pages: web 10 | 11 | gem: 12 | gemspec: pkg/rdoc-shomen.gemspec 13 | 14 | dnote: 15 | labels: ~ 16 | title: Source Notes 17 | output: log/notes.html 18 | 19 | locat: 20 | active: false 21 | output: log/locat.html 22 | 23 | vclog: 24 | output: 25 | - log/history.rdoc 26 | - log/changes.rdoc 27 | 28 | # has bug, fix first 29 | #mast: 30 | # active: true 31 | 32 | -------------------------------------------------------------------------------- /lib/rdoc/discover.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "rubygems" 3 | rescue LoadError 4 | end 5 | 6 | begin 7 | gem 'rdoc', '~> 3' 8 | #gem 'shomen' 9 | require_relative 'generator/shomen' 10 | rescue Gem::LoadError => error 11 | puts error 12 | rescue LoadError => error 13 | puts error 14 | end 15 | 16 | 17 | 18 | 19 | 20 | #puts "RDoc discovered Shomen!" if $DEBUG 21 | 22 | # If using Gems put her on the $LOAD_PATH 23 | #begin 24 | # require "rubygems" 25 | # gem "rdoc", ">= 2.5" 26 | # gem "shomen" 27 | #end 28 | 29 | #require 'shomen/rdoc/option_fix' 30 | 31 | #RDoc.generator_option('shomen') do 32 | # require 'shomen/rdoc/generator' 33 | # RDoc::Generator::Shomen 34 | #end 35 | 36 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Shomen RDoc Generator 2 | 3 | {Homepage}[http://rubyworks.github.com/rdoc-shomen] / 4 | {Report Issue}[http://github.com/rubyworks/rdoc-shomen/issues] / 5 | {Source Code}[http://github.com/rubyworks/rdoc-shomen] / 6 | {Mailing List}[http://google.groups.com/groups/rubyworks-mailinglist] 7 | 8 | {}[http://travis-ci.org/rubyworks/rdoc-shomen] 9 | 10 | 11 | == Description 12 | 13 | This is the old-school Shomen generator for RDoc. 14 | 15 | 16 | == Instruction 17 | 18 | Use the shomen generator like any other RDoc generator. 19 | 20 | $ rdoc -f shomen -m README.rdoc lib [A-Z]*.* 21 | 22 | The shomen document will be saved to the output directory (default `doc`) 23 | as wither `doc.json` or as `-.json` if project metadata is 24 | discoverable. 25 | 26 | The generator supports a couple of special options: 27 | 28 | * `--yaml` option will save the file to YAML format instead of JSON. 29 | * `--source` will include full source code in script entries. 30 | 31 | The `--source` option is not recommended for documentation that will 32 | be served online as it will make the documation file rather large. 33 | 34 | 35 | == Copyrights 36 | 37 | Copyright (c) 2011 Rubyworks. All rights reserved. 38 | 39 | RDoc Shomen is distributable in accordance with the *BSD-2-Clause* license. 40 | 41 | See LICENSE.txt file for details. 42 | 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | RDoc Shomen - Shomen Generator for RDoc 2 | 3 | Copyright (c) 2011 Rubyworks. All rights reserved. 4 | 5 | Licensed (spdx) BSD-2-Clause 6 | 7 | Redistribution and use in source and binary forms, 8 | with or without modification, are permitted provided that the following 9 | conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | (http://github.com/rubyworks/rdoc-shomen) 30 | 31 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | source: 3 | - meta 4 | authors: 5 | - name: trans 6 | email: transfire@gmail.com 7 | copyrights: 8 | - holder: Rubyworks 9 | year: '2011' 10 | license: BSD-2-Clause 11 | requirements: 12 | - name: shomen-model 13 | - name: citron 14 | groups: 15 | - test 16 | development: true 17 | - name: detroit 18 | groups: 19 | - build 20 | development: true 21 | dependencies: [] 22 | alternatives: [] 23 | conflicts: [] 24 | repositories: 25 | - uri: git://github.com/rubyworks/rdoc-shomen.git 26 | scm: git 27 | name: upstream 28 | resources: 29 | - uri: http://rubyworks.github.com/rdoc-shomen 30 | name: home 31 | type: home 32 | - uri: http://rubydoc.info/gems/rdoc-shoment 33 | name: docs 34 | type: doc 35 | - uri: http://github.com/rubyworks/rdoc-shomen 36 | name: code 37 | type: code 38 | - uri: http://github.com/rubyworks/rdoc-shomen/issues 39 | name: bugs 40 | type: bugs 41 | - uri: http://groups.google.com/groups/rubyworks-mailinglist 42 | name: mail 43 | type: mail 44 | extra: {} 45 | load_path: 46 | - lib 47 | revision: 0 48 | created: '2010-07-01' 49 | summary: RDoc Generator for Shomen Documentation Format 50 | title: RDoc Shomen 51 | version: 0.1.1 52 | name: rdoc-shomen 53 | description: ! 'RDoc-Shomen is an RDoc generator plugin that can be used to generate 54 | Shomen 55 | 56 | documentation. This is an alternative to the shomen command line tool which 57 | 58 | use the `.rdoc` cache to generate a shomen document. In contrast the rdoc-shomen 59 | 60 | generator operates as a traditional rdoc plugin.' 61 | organization: rubyworks 62 | date: '2012-04-16' 63 | -------------------------------------------------------------------------------- /lib/rdoc/generator/shomen_extensions.rb: -------------------------------------------------------------------------------- 1 | class RDoc::Options 2 | # true/false to include full source 3 | attr_accessor :source 4 | # true/false to use YAML instead of JSON 5 | attr_accessor :yaml 6 | end 7 | 8 | class RDoc::TopLevel 9 | # 10 | def to_h 11 | { 12 | :path => path, 13 | :name => base_name, 14 | :fullname => full_name, 15 | :rootname => absolute_name, 16 | :modified => last_modified, 17 | :diagram => diagram 18 | } 19 | end 20 | 21 | # 22 | #def to_json 23 | # to_h.to_json 24 | #end 25 | end 26 | 27 | 28 | class RDoc::ClassModule 29 | # 30 | def with_documentation? 31 | document_self_or_methods || classes_and_modules.any?{ |c| c.with_documentation? } 32 | end 33 | 34 | # 35 | def document_self_or_methods 36 | document_self || method_list.any?{ |m| m.document_self } 37 | end 38 | 39 | # # 40 | # def to_h 41 | # { 42 | # :name => name, 43 | # :fullname => full_name, 44 | # :type => type, 45 | # :path => path, 46 | # :superclass => module? ? nil : superclass 47 | # } 48 | # end 49 | # 50 | # def to_json 51 | # to_h.to_json 52 | # end 53 | end 54 | 55 | 56 | module RDoc::SourceCodeAccess 57 | 58 | # 59 | def source_code_raw 60 | return '' unless @token_stream 61 | src = "" 62 | @token_stream.each do |t| 63 | next unless t 64 | src << t.text 65 | end 66 | #add_line_numbers(src) 67 | src 68 | end 69 | 70 | # 71 | def source_code_location 72 | src = source_code_raw 73 | if md = /File (.*?), line (\d+)/.match(src) 74 | file = md[1] 75 | line = md[2] 76 | else 77 | file = "(unknown)" 78 | line = 0 79 | end 80 | return file, line 81 | end 82 | 83 | end 84 | 85 | 86 | class RDoc::AnyMethod 87 | include RDoc::SourceCodeAccess 88 | 89 | # # NOTE: dont_rename_initialize isn't used 90 | # def to_h 91 | # { 92 | # :name => name, 93 | # :fullname => full_name, 94 | # :prettyname => pretty_name, 95 | # :path => path, 96 | # :type => type, 97 | # :visibility => visibility, 98 | # :blockparams => block_params, 99 | # :singleton => singleton, 100 | # :text => text, 101 | # :aliases => aliases, 102 | # :aliasfor => is_alias_for, 103 | # :aref => aref, 104 | # :parms => params, 105 | # :callseq => call_seq 106 | # #:paramseq => param_seq, 107 | # } 108 | # end 109 | 110 | # # 111 | # def to_json 112 | # to_h.to_json 113 | # end 114 | end 115 | 116 | class RDoc::Attr 117 | include RDoc::SourceCodeAccess 118 | end 119 | 120 | =begin 121 | 122 | # DEPRECATE ASAP 123 | require "rdoc/parser/c" 124 | # New RDoc somehow misses class comemnts. 125 | # copyied this function from "2.2.2" 126 | if ['2.4.2', '2.4.3'].include? RDoc::VERSION 127 | class RDoc::Parser::C 128 | def find_class_comment(class_name, class_meth) 129 | comment = nil 130 | if @content =~ %r{((?>/\*.*?\*/\s+)) 131 | (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)\)}xmi then 132 | comment = $1 133 | elsif @content =~ %r{Document-(?:class|module):\s#{class_name}\s*?(?:<\s+[:,\w]+)?\n((?>.*?\*/))}m 134 | comment = $1 135 | else 136 | if @content =~ /rb_define_(class|module)/m then 137 | class_name = class_name.split("::").last 138 | comments = [] 139 | @content.split(/(\/\*.*?\*\/)\s*?\n/m).each_with_index do |chunk, index| 140 | comments[index] = chunk 141 | if chunk =~ /rb_define_(class|module).*?"(#{class_name})"/m then 142 | comment = comments[index-1] 143 | break 144 | end 145 | end 146 | end 147 | end 148 | class_meth.comment = mangle_comment(comment) if comment 149 | end 150 | end 151 | end 152 | 153 | =end 154 | 155 | -------------------------------------------------------------------------------- /pkg/rdoc-shomen.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # 11 | DOTRUBY = '{,../}.ruby' unless defined?(DOTRUBY) 12 | 13 | # 14 | MANIFEST = '{,../}manifest{,.txt}' unless defined?(MANIFEST) 15 | 16 | # For which revision of .ruby is this gemspec intended? 17 | REVISION = 0 unless defined?(REVISION) 18 | 19 | # 20 | PATTERNS = { 21 | :bin_files => 'bin/*', 22 | :lib_files => 'lib/{**/}*.rb', 23 | :ext_files => 'ext/{**/}extconf.rb', 24 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 25 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 26 | } unless defined?(PATTERNS) 27 | 28 | # 29 | def self.instance 30 | new.to_gemspec 31 | end 32 | 33 | attr :metadata 34 | 35 | attr :manifest 36 | 37 | # 38 | def initialize 39 | @metadata = YAML.load_file(Dir.glob(DOTRUBY).first) 40 | @manifest = Dir.glob(MANIFEST, File::FNM_CASEFOLD).first 41 | 42 | if @metadata['revision'].to_i != REVISION 43 | warn "You have the wrong revision. Trying anyway..." 44 | end 45 | end 46 | 47 | # 48 | def scm 49 | @scm ||= \ 50 | case 51 | when File.directory?('.git') 52 | :git 53 | end 54 | end 55 | 56 | # 57 | def files 58 | @files ||= \ 59 | #glob_files[patterns[:files]] 60 | case 61 | when manifest 62 | File.readlines(manifest). 63 | map{ |line| line.strip }. 64 | reject{ |line| line.empty? || line[0,1] == '#' } 65 | when scm == :git 66 | `git ls-files -z`.split("\0") 67 | else 68 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 69 | end.select{ |path| File.file?(path) } 70 | end 71 | 72 | # 73 | def glob_files(pattern) 74 | Dir.glob(pattern).select { |path| 75 | File.file?(path) && files.include?(path) 76 | } 77 | end 78 | 79 | # 80 | def patterns 81 | PATTERNS 82 | end 83 | 84 | # 85 | def executables 86 | @executables ||= \ 87 | glob_files(patterns[:bin_files]).map do |path| 88 | File.basename(path) 89 | end 90 | end 91 | 92 | def extensions 93 | @extensions ||= \ 94 | glob_files(patterns[:ext_files]).map do |path| 95 | File.basename(path) 96 | end 97 | end 98 | 99 | # 100 | def name 101 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 102 | end 103 | 104 | # 105 | def to_gemspec 106 | Gem::Specification.new do |gemspec| 107 | gemspec.name = name 108 | gemspec.version = metadata['version'] 109 | gemspec.summary = metadata['summary'] 110 | gemspec.description = metadata['description'] 111 | 112 | metadata['authors'].each do |author| 113 | gemspec.authors << author['name'] 114 | 115 | if author.has_key?('email') 116 | if gemspec.email 117 | gemspec.email << author['email'] 118 | else 119 | gemspec.email = [author['email']] 120 | end 121 | end 122 | end 123 | 124 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 125 | 126 | metadata['requirements'].each do |req| 127 | next if req['optional'] 128 | 129 | name = req['name'] 130 | version = req['version'] 131 | groups = req['groups'] || [] 132 | 133 | case version 134 | when /^(.*?)\+$/ 135 | version = ">= #{$1}" 136 | when /^(.*?)\-$/ 137 | version = "< #{$1}" 138 | when /^(.*?)\~$/ 139 | version = "~> #{$1}" 140 | end 141 | 142 | if groups.empty? or groups.include?('runtime') 143 | # populate runtime dependencies 144 | if gemspec.respond_to?(:add_runtime_dependency) 145 | gemspec.add_runtime_dependency(name,*version) 146 | else 147 | gemspec.add_dependency(name,*version) 148 | end 149 | else 150 | # populate development dependencies 151 | if gemspec.respond_to?(:add_development_dependency) 152 | gemspec.add_development_dependency(name,*version) 153 | else 154 | gemspec.add_dependency(name,*version) 155 | end 156 | end 157 | end 158 | 159 | # convert external dependencies into a requirements 160 | if metadata['external_dependencies'] 161 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 162 | metadata['external_dependencies'].each do |req| 163 | gemspec.requirements << req.to_s 164 | end 165 | end 166 | 167 | # determine homepage from resources 168 | homepage = metadata['resources'].find{ |r| r['type'] =~ /^home/ || r['name'] =~ /^(home|web)/ } 169 | gemspec.homepage = homepage['uri'] if homepage 170 | 171 | gemspec.require_paths = metadata['load_path'] || ['lib'] 172 | gemspec.post_install_message = metadata['install_message'] 173 | 174 | # RubyGems specific metadata 175 | gemspec.files = files 176 | gemspec.extensions = extensions 177 | gemspec.executables = executables 178 | 179 | if Gem::VERSION < '1.7.' 180 | gemspec.default_executable = gemspec.executables.first 181 | end 182 | 183 | gemspec.test_files = glob_files(patterns[:test_files]) 184 | 185 | unless gemspec.files.include?('.document') 186 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 187 | end 188 | end 189 | end 190 | 191 | end #class GemSpec 192 | 193 | end 194 | 195 | DotRuby::GemSpec.instance 196 | -------------------------------------------------------------------------------- /lib/rdoc/generator/shomen.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'pathname' 3 | require 'yaml' 4 | require 'json' 5 | 6 | require 'shomen-model' 7 | 8 | require 'rdoc/rdoc' 9 | require 'rdoc/generator' 10 | require 'rdoc/generator/markup' 11 | require 'rdoc/generator/shomen_extensions' 12 | 13 | # Shomen Adaptor for RDoc utilizes the rdoc tool to parse ruby source code 14 | # to build a Shomen documenation file. 15 | # 16 | # RDoc is almost entirely a free-form documentation system, so it is not 17 | # possible for Shomen to fully harness all the details it can support from 18 | # the RDoc documentation, such as method argument descriptions. 19 | # 20 | class RDoc::Generator::Shomen 21 | 22 | # 23 | DESCRIPTION = 'Shomen documentation format' 24 | 25 | # Register shomen generator with RDoc. 26 | RDoc::RDoc.add_generator(self) 27 | 28 | #include RDocShomen::Metadata 29 | 30 | # Standard generator factory method. 31 | # 32 | # options - Generator options. 33 | # 34 | # Returns new RDoc::Generator::Shomen instance. 35 | def self.for(options) 36 | new(options) 37 | end 38 | 39 | # User options from the command line. 40 | attr :options 41 | 42 | # 43 | def self.setup_options(options) 44 | options.source = false 45 | options.yaml = false 46 | 47 | opt = options.option_parser 48 | 49 | opt.separator nil 50 | opt.separator "Shomen generator options:" 51 | opt.separator nil 52 | opt.on("--yaml", "Generate YAML document instead of JSON.") do |value| 53 | options.yaml = true 54 | end 55 | opt.separator nil 56 | opt.on("--source", "Include full source code for scripts.") do |value| 57 | options.github = true 58 | end 59 | end 60 | 61 | # List of all classes and modules. 62 | #def all_classes_and_modules 63 | # @all_classes_and_modules ||= RDoc::TopLevel.all_classes_and_modules 64 | #end 65 | 66 | # In the world of the RDoc Generators #classes is the same 67 | # as #all_classes_and_modules. Well, except that its sorted 68 | # too. For classes sans modules, see #types. 69 | # 70 | def classes 71 | @classes ||= RDoc::TopLevel.all_classes_and_modules.sort 72 | end 73 | 74 | # Only toplevel classes and modules. 75 | def classes_toplevel 76 | @classes_toplevel ||= classes.select {|klass| !(RDoc::ClassModule === klass.parent) } 77 | end 78 | 79 | # 80 | def files 81 | @files ||= ( 82 | @files_rdoc.select{ |f| f.parser != RDoc::Parser::Simple } 83 | ) 84 | end 85 | 86 | # List of toplevel files. RDoc supplies this via the #generate method. 87 | def files_toplevel 88 | @files_toplevel ||= ( 89 | @files_rdoc.select{ |f| f.parser == RDoc::Parser::Simple } 90 | ) 91 | end 92 | 93 | # 94 | def files_hash 95 | @files ||= RDoc::TopLevel.files_hash 96 | end 97 | 98 | # List of all methods in all classes and modules. 99 | def methods_all 100 | @methods_all ||= classes.map{ |m| m.method_list }.flatten.sort 101 | end 102 | 103 | # List of all attributes in all classes and modules. 104 | def attributes_all 105 | @attributes_all ||= classes.map{ |m| m.attributes }.flatten.sort 106 | end 107 | 108 | # 109 | def constants_all 110 | @constants_all ||= classes.map{ |c| c.constants }.flatten 111 | end 112 | 113 | ## TODO: What's this then? 114 | ##def json_creatable? 115 | ## RDoc::TopLevel.json_creatable? 116 | ##end 117 | 118 | # RDoc needs this to function. 119 | def class_dir ; nil ; end 120 | 121 | # RDoc needs this to function. 122 | def file_dir ; nil ; end 123 | 124 | # TODO: Rename ? 125 | def shomen 126 | @table || {} 127 | end 128 | 129 | # Build the initial indices and output objects based on an array of 130 | # top level objects containing the extracted information. 131 | # 132 | # files - Files to document. 133 | # 134 | # Returns nothing. 135 | def generate(files) 136 | @files_rdoc = files.sort 137 | 138 | @table = {} 139 | 140 | generate_metadata 141 | generate_constants 142 | generate_classes 143 | #generate_attributes 144 | generate_methods 145 | generate_documents 146 | generate_scripts # must be last b/c it depends on the others 147 | 148 | # TODO: method accessor fields need to be handled 149 | 150 | # THINK: Internal referencing model, YAML and JSYNC ? 151 | #ref_table = reference_table(@table) 152 | 153 | if options.yaml 154 | out = @table.to_yaml 155 | else 156 | out = JSON.generate(@table) 157 | end 158 | 159 | if options.op_dir == '-' 160 | puts out 161 | else 162 | File.open(output_file, 'w') do |f| 163 | f << out 164 | end unless $dryrun 165 | end 166 | 167 | #rescue StandardError => err 168 | # debug_msg "%s: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ] 169 | # raise err 170 | end 171 | 172 | # 173 | def output_file 174 | name = project_metadata['name'] 175 | vers = project_metadata['version'] 176 | 177 | if name && vers 178 | "#{name}-#{vers}.json" 179 | else 180 | 'doc.json' 181 | end 182 | end 183 | 184 | protected 185 | 186 | # Initialize new generator. 187 | # 188 | # options - Generator options. 189 | # 190 | # Returns new RDoc::Generator::Shomen instance. 191 | def initialize(options) 192 | @options = options 193 | #@options.diagram = false # why? 194 | 195 | @path_base = Pathname.pwd.expand_path 196 | 197 | # TODO: This is probably not needed any more. 198 | @path_output = Pathname.new(@options.op_dir).expand_path(@path_base) 199 | end 200 | 201 | # Current pathname. 202 | attr :path_base 203 | 204 | # The output path. 205 | attr :path_output 206 | 207 | # 208 | def path_output_relative(path=nil) 209 | if path 210 | path.to_s.sub(path_base.to_s+'/', '') 211 | else 212 | @path_output_relative ||= path_output.to_s.sub(path_base.to_s+'/', '') 213 | end 214 | end 215 | 216 | # 217 | def project_metadata 218 | @project_metadata ||= Shomen::Metadata.new 219 | end 220 | 221 | # 222 | def generate_metadata 223 | @table['(metadata)'] = project_metadata.to_h 224 | end 225 | 226 | # Add constants to table. 227 | def generate_constants 228 | debug_msg "Generating constant documentation:" 229 | constants_all.each do |rdoc| 230 | model = Shomen::Model::Constant.new 231 | 232 | model.path = rdoc.parent.full_name + '::' + rdoc.name 233 | model.name = rdoc.name 234 | model.namespace = rdoc.parent.full_name 235 | model.comment = comment(rdoc.comment) 236 | model.format = 'rdoc' 237 | model.value = rdoc.value 238 | model.files = ["/#{rdoc.file.full_name}"] 239 | 240 | @table[model.path] = model.to_h 241 | end 242 | end 243 | 244 | # Add classes (and modules) to table. 245 | def generate_classes 246 | debug_msg "Generating class/module documentation:" 247 | 248 | classes.each do |rdoc_class| 249 | debug_msg "%s (%s)" % [ rdoc_class.full_name, rdoc_class.path ] 250 | 251 | if rdoc_class.type=='class' 252 | model = Shomen::Model::Class.new 253 | else 254 | model = Shomen::Model::Module.new 255 | end 256 | 257 | model.path = rdoc_class.full_name 258 | model.name = rdoc_class.name 259 | model.namespace = rdoc_class.full_name.split('::')[0...-1].join('::') 260 | model.includes = rdoc_class.includes.map{ |x| x.name } # FIXME: How to "lookup" full name? 261 | model.extensions = [] # TODO: How to get extensions? 262 | model.comment = comment(rdoc_class.comment) 263 | model.format = 'rdoc' 264 | model.constants = rdoc_class.constants.map{ |x| complete_name(x.name, rdoc_class.full_name) } 265 | model.modules = rdoc_class.modules.map{ |x| complete_name(x.name, rdoc_class.full_name) } 266 | model.classes = rdoc_class.classes.map{ |x| complete_name(x.name, rdoc_class.full_name) } 267 | model.methods = rdoc_class.method_list.map{ |m| method_name(m) }.uniq 268 | model.accessors = rdoc_class.attributes.map{ |a| method_name(a) }.uniq #+ ":#{a.rw}" }.uniq 269 | model.files = rdoc_class.in_files.map{ |x| "/#{x.full_name}" } 270 | 271 | if rdoc_class.type == 'class' 272 | # HACK: No idea why RDoc is returning some weird superclass: 273 | # ] aliases: []> 276 | # Maybe it has something to do with #fileutils? 277 | model.superclass = ( 278 | case rdoc_class.superclass 279 | when nil 280 | when String 281 | rdoc_class.superclass 282 | else 283 | rdoc_class.superclass.full_name 284 | end 285 | ) 286 | end 287 | 288 | @table[model.path] = model.to_h 289 | end 290 | end 291 | 292 | # TODO: How to get literal interface separate from call-sequnces? 293 | 294 | # Transform RDoc methods to Shomen model and add to table. 295 | def generate_methods 296 | debug_msg "Generating method documentation:" 297 | 298 | list = methods_all + attributes_all 299 | 300 | list.each do |rdoc_method| 301 | #debug_msg "%s" % [rdoc_method.full_name] 302 | 303 | #full_name = method_name(m) 304 | #'prettyname' => m.pretty_name, 305 | #'type' => m.type, # class or instance 306 | 307 | model = Shomen::Model::Method.new 308 | 309 | model.path = method_name(rdoc_method) 310 | model.name = rdoc_method.name 311 | model.namespace = rdoc_method.parent_name 312 | model.comment = comment(rdoc_method.comment) 313 | model.format = 'rdoc' 314 | model.aliases = rdoc_method.aliases.map{ |a| method_name(a) } 315 | model.alias_for = method_name(rdoc_method.is_alias_for) 316 | model.singleton = rdoc_method.singleton 317 | 318 | model.declarations << rdoc_method.type.to_s #singleton ? 'class' : 'instance' 319 | model.declarations << rdoc_method.visibility.to_s 320 | 321 | model.interfaces = [] 322 | if rdoc_method.call_seq 323 | rdoc_method.call_seq.split("\n").each do |cs| 324 | cs = cs.to_s.strip 325 | model.interfaces << parse_interface(cs) unless cs == '' 326 | end 327 | end 328 | model.interfaces << parse_interface("#{rdoc_method.name}#{rdoc_method.params}") 329 | 330 | model.returns = [] # RDoc doesn't support specifying return values 331 | model.file = '/'+rdoc_method.source_code_location.first 332 | model.line = rdoc_method.source_code_location.last.to_i 333 | model.source = rdoc_method.source_code_raw 334 | 335 | if rdoc_method.respond_to?(:c_function) 336 | model.language = rdoc_method.c_function ? 'c' : 'ruby' 337 | else 338 | model.language = 'ruby' 339 | end 340 | 341 | @table[model.path] = model.to_h 342 | end 343 | end 344 | 345 | #-- 346 | =begin 347 | # 348 | def generate_attributes 349 | #$stderr.puts "HERE!" 350 | #$stderr.puts attributes_all.inspect 351 | #exit 352 | debug_msg "Generating attributes documentation:" 353 | attributes_all.each do |rdoc_attribute| 354 | debug_msg "%s" % [rdoc_attribute.full_name] 355 | 356 | adapter = Shomen::RDoc::MethodAdapter.new(rdoc_attribute) 357 | data = Shomen::Model::Method.new(adapter).to_h 358 | 359 | @table[data['path']] = data 360 | 361 | #code = m.source_code_raw 362 | #file, line = m.source_code_location 363 | 364 | #full_name = method_name(m) 365 | 366 | #'prettyname' => m.pretty_name, 367 | #'type' => m.type, # class or instance 368 | 369 | #model_class = m.singleton ? Shomen::Model::Function : Shomen::Model::Method 370 | #model_class = Shomen::Model::Attribute 371 | 372 | #@table[full_name] = model_class.new( 373 | # 'path' => full_name, 374 | # 'name' => m.name, 375 | # 'namespace' => m.parent_name, 376 | # 'comment' => comment(m.comment), 377 | # 'access' => m.visibility.to_s, 378 | # 'rw' => m.rw, # TODO: better name ? 379 | # 'singleton' => m.singleton, 380 | # 'aliases' => m.aliases.map{ |a| method_name(a) }, 381 | # 'alias_for' => method_name(m.is_alias_for), 382 | # 'image' => m.params, 383 | # 'arguments' => [], 384 | # 'parameters' => [], 385 | # 'block' => m.block_params, # TODO: what is block? 386 | # 'interface' => m.arglists, 387 | # 'returns' => [], 388 | # 'file' => file, 389 | # 'line' => line, 390 | # 'source' => code 391 | #).to_h 392 | end 393 | end 394 | =end 395 | #++ 396 | 397 | # Parse method interface. 398 | # 399 | # TODO: remove any trailing comment too 400 | def parse_interface(interface) 401 | args, block = [], {} 402 | 403 | interface, returns = interface.split(/[=-]\>/) 404 | interface = interface.strip 405 | if i = interface.index(/\)\s*\{/) 406 | block['image'] = interface[i+1..-1].strip 407 | interface = interface[0..i].strip 408 | end 409 | 410 | arguments = interface.strip.sub(/^.*?\(/,'').chomp(')') 411 | arguments = arguments.split(/\s*\,\s*/) 412 | arguments.each do |a| 413 | if a.start_with?('&') 414 | block['name'] = a 415 | else 416 | n,v = a.split('=') 417 | args << (v ? {'name'=>n,'default'=>v} : {'name'=>n}) 418 | end 419 | end 420 | 421 | result = {} 422 | result['signature'] = interface 423 | result['arguments'] = args 424 | result['block'] = block unless block.empty? 425 | result['returns'] = returns.strip if returns 426 | return result 427 | end 428 | private :parse_interface 429 | 430 | # Generate entries for information files, e.g. `README.rdoc`. 431 | def generate_documents 432 | files_toplevel.each do |rdoc_document| 433 | absolute_path = File.join(path_base, rdoc_document.full_name) 434 | 435 | model = Shomen::Model::Document.new 436 | 437 | model.path = rdoc_document.full_name 438 | model.name = File.basename(absolute_path) 439 | model.created = File.mtime(absolute_path) 440 | model.modified = File.mtime(absolute_path) 441 | model.text = File.read(absolute_path) #comment(file.comment) 442 | model.format = mime_type(absolute_path) 443 | 444 | @table['/'+model.path] = model.to_h 445 | end 446 | end 447 | 448 | # TODO: Add loadpath and make file path relative to it? 449 | 450 | # Generate script entries. 451 | def generate_scripts 452 | #debug_msg "Generating file documentation in #{path_output_relative}:" 453 | #templatefile = self.path_template + 'file.rhtml' 454 | 455 | files.each do |rdoc_file| 456 | debug_msg "%s" % [rdoc_file.full_name] 457 | 458 | absolute_path = File.join(path_base, rdoc_file.full_name) 459 | #rel_prefix = self.path_output.relative_path_from(outfile.dirname) 460 | 461 | model = Shomen::Model::Script.new 462 | 463 | model.path = rdoc_file.full_name 464 | model.name = File.basename(rdoc_file.full_name) 465 | model.created = File.ctime(absolute_path) 466 | model.modified = File.mtime(absolute_path) 467 | 468 | if options.source 469 | model.source = File.read(absolute_path) #comment(file.comment) 470 | model.language = mime_type(absolute_path) 471 | end 472 | 473 | webcvs = options.webcvs || project_metadata['webcvs'] 474 | if webcvs 475 | model.uri = File.join(webcvs, model.path) # TODO: use open-uri ? 476 | model.language = mime_type(absolute_path) 477 | end 478 | 479 | #model.header = 480 | #model.footer = 481 | model.requires = rdoc_file.requires.map{ |r| r.name } 482 | model.constants = rdoc_file.constants.map{ |c| c.full_name } 483 | 484 | # note that this utilizes the table we are building 485 | # so it needs to be the last thing done. 486 | @table.each do |k, h| 487 | case h['!'] 488 | when 'module' 489 | model.modules ||= [] 490 | model.modules << k if h['files'].include?(rdoc_file.full_name) 491 | when 'class' 492 | model.classes ||= [] 493 | model.classes << k if h['files'].include?(rdoc_file.full_name) 494 | when 'method' 495 | model.methods ||= [] 496 | model.methods << k if h['file'] == rdoc_file.full_name 497 | when 'class-method' 498 | model.class_methods ||= [] 499 | model.class_methods << k if h['file'] == rdoc_file.full_name 500 | end 501 | end 502 | 503 | @table['/'+model.path] = model.to_h 504 | end 505 | end 506 | 507 | # Returns String of fully qualified name. 508 | def complete_name(name, namespace) 509 | if name !~ /^#{namespace}/ 510 | "#{namespace}::#{name}" 511 | else 512 | name 513 | end 514 | end 515 | 516 | # 517 | def collect_methods(class_module, singleton=false) 518 | list = [] 519 | class_module.method_list.each do |m| 520 | next if singleton ^ m.singleton 521 | list << method_name(m) 522 | end 523 | list.uniq 524 | end 525 | 526 | # 527 | def collect_attributes(class_module, singleton=false) 528 | list = [] 529 | class_module.attributes.each do |a| 530 | next if singleton ^ a.singleton 531 | #p a.rw 532 | #case a.rw 533 | #when :write, 'W' 534 | # list << "#{method_name(a)}=" 535 | #else 536 | list << method_name(a) 537 | #end 538 | end 539 | list.uniq 540 | end 541 | 542 | # 543 | def method_name(method) 544 | return nil if method.nil? 545 | if method.singleton 546 | i = method.full_name.rindex('::') 547 | method.full_name[0...i] + '.' + method.full_name[i+2..-1] 548 | else 549 | method.full_name 550 | end 551 | end 552 | 553 | # 554 | def mime_type(path) 555 | case File.extname(path) 556 | when '.rb', '.rbx' then 'text/ruby' 557 | when '.c' then 'text/c-source' 558 | when '.rdoc' then 'text/rdoc' 559 | when '.md', '.markdown' then 'text/markdown' 560 | else 'text/plain' 561 | end 562 | end 563 | 564 | # Output progress information if rdoc debugging is enabled 565 | 566 | def debug_msg(msg) 567 | return unless $DEBUG_RDOC 568 | case msg[-1,1] 569 | when '.' then tab = "= " 570 | when ':' then tab = "== " 571 | else tab = "* " 572 | end 573 | $stderr.puts(tab + msg) 574 | end 575 | 576 | # 577 | def comment(rdoc_comment) 578 | case rdoc_comment 579 | when String 580 | "" # something is wrong if this is a string 581 | else 582 | rdoc_comment.text 583 | end 584 | end 585 | 586 | end 587 | 588 | 589 | 590 | #-- 591 | =begin 592 | # 593 | # N O T U S E D 594 | # 595 | 596 | # Sort based on how often the top level namespace occurs, and then on the 597 | # name of the module -- this works for projects that put their stuff into 598 | # a namespace, of course, but doesn't hurt if they don't. 599 | def sort_salient(classes) 600 | nscounts = classes.inject({}) do |counthash, klass| 601 | top_level = klass.full_name.gsub( /::.*/, '' ) 602 | counthash[top_level] ||= 0 603 | counthash[top_level] += 1 604 | counthash 605 | endfiles_toplevel 606 | classes.sort_by{ |klass| 607 | top_level = klass.full_name.gsub( /::.*/, '' ) 608 | [nscounts[top_level] * -1, klass.full_name] 609 | }.select{ |klass| 610 | klass.document_self 611 | } 612 | end 613 | =end 614 | 615 | =begin 616 | # Loop through table and convert all named references into bonofied object 617 | # references. 618 | def reference_table(table) 619 | debug_msg "== Generating Reference Table" 620 | new_table = {} 621 | table.each do |key, entry| 622 | debug_msg "%s" % [key] 623 | data = entry.dup 624 | new_table[key] = data 625 | case data['!'] 626 | when 'script' 627 | data["constants"] = ref_list(data["constants"]) 628 | data["modules"] = ref_list(data["modules"]) 629 | data["classes"] = ref_list(data["classes"]) 630 | data["functions"] = ref_list(data["functions"]) 631 | data["methods"] = ref_list(data["methods"]) 632 | when 'file' 633 | when 'constant' 634 | data["namespace"] = ref_item(data["namespace"]) 635 | when 'module', 'class' 636 | data["namespace"] = ref_item(data["namespace"]) 637 | data["includes"] = ref_list(data["includes"]) 638 | #data["extended"] = ref_list(data["extended"]) 639 | data["constants"] = ref_list(data["constants"]) 640 | data["modules"] = ref_list(data["modules"]) 641 | data["classes"] = ref_list(data["classes"]) 642 | data["functions"] = ref_list(data["functions"]) 643 | data["methods"] = ref_list(data["methods"]) 644 | data["files"] = ref_list(data["files"]) 645 | data["superclass"] = ref_item(data["superclass"]) if data.key?("superclass") 646 | when 'method', 'function' 647 | data["namespace"] = ref_item(data["namespace"]) 648 | data["file"] = ref_item(data["file"]) 649 | end 650 | end 651 | new_table 652 | end 653 | 654 | # Given a key, return the matching table item. If not found return the key. 655 | def ref_item(key) 656 | @table[key] || key 657 | end 658 | 659 | # Given a list of keys, return the matching table items. 660 | def ref_list(keys) 661 | #keys.map{ |k| @table[k] || k } 662 | keys.map{ |k| @table[k] || nil }.compact 663 | end 664 | 665 | =end 666 | #++ 667 | 668 | --------------------------------------------------------------------------------