Commit 33bc2d40 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Large refactoring of JsDuck::App class.

Splitting the output routines into three classes:

- StdoutExporter
- JsonExporter
- HtmlExporter

Additionally creating ClassWriter class for outputting the .js or .json
files. This one isn't pretty, but I have to share the output logic
between JsonExporter and StdoutExporter.

Another nastiness is the format_classes method which has to run BEFORE
writing out classes JSON, but AFTER the source HTML files have been
created - because of the parallel stuff. Brr...

At least the App class now has less responsibilities, but further
refactorings are needed.
parent d88c15fe
Loading
Loading
Loading
Loading
+18 −165
Original line number Diff line number Diff line
require 'rubygems'
require 'jsduck/aggregator'
require 'jsduck/source_file'
require 'jsduck/source_writer'
require 'jsduck/doc_formatter'
require 'jsduck/class_formatter'
require 'jsduck/class'
require 'jsduck/icons'
require 'jsduck/search_data'
require 'jsduck/stats'
require 'jsduck/relations'
require 'jsduck/inherit_doc'
require 'jsduck/exporter'
require 'jsduck/renderer'
require 'jsduck/parallel_wrap'
require 'jsduck/logger'
require 'jsduck/welcome'
@@ -22,7 +16,9 @@ require 'jsduck/categories'
require 'jsduck/images'
require 'jsduck/json_duck'
require 'jsduck/lint'
require 'fileutils'
require 'jsduck/stdout_exporter'
require 'jsduck/json_exporter'
require 'jsduck/html_exporter'

module JsDuck

@@ -46,47 +42,24 @@ module JsDuck
      InheritDoc.new(@relations).resolve_all
      Lint.new(@relations).run

      @images = Images.new(@opts.images)
      @welcome = Welcome.create(@opts.welcome)
      @guides = Guides.create(@opts.guides, get_doc_formatter)
      @videos = Videos.create(@opts.videos)
      @examples = Examples.create(@opts.examples)
      @categories = Categories.create(@opts.categories_path, get_doc_formatter, @relations)
      @resources = {
        :parsed_files => parsed_files,
        :images => Images.new(@opts.images),
        :welcome => Welcome.create(@opts.welcome),
        :guides => Guides.create(@opts.guides, DocFormatter.new(@relations, @opts)),
        :videos => Videos.create(@opts.videos),
        :examples => Examples.create(@opts.examples),
        :categories => Categories.create(@opts.categories_path, DocFormatter.new(@relations, @opts), @relations),
      }


      clear_output_dir unless @opts.export == :stdout
      if @opts.export == :stdout
        format_classes
        puts JsonDuck.generate(@relations.classes)
        StdoutExporter.new(@relations, @opts).run { format_classes }
      elsif @opts.export == :json
        FileUtils.mkdir(@opts.output_dir)
        format_classes
        write_classes
      else
        if @opts.template_links
          link_template
        JsonExporter.new(@relations, @opts).run { format_classes }
      else
          copy_template
        end
        create_template_html
        create_print_template_html
        if !@opts.seo
          FileUtils.rm(@opts.output_dir+"/index.php")
          FileUtils.cp(@opts.output_dir+"/template.html", @opts.output_dir+"/index.html")
        end
        if @opts.eg_iframe
          FileUtils.rm(@opts.output_dir+"/eg-iframe.html")
          FileUtils.cp(@opts.eg_iframe, @opts.output_dir+"/eg-iframe.html")
        HtmlExporter.new(@relations, @resources, @opts).run { format_classes }
      end
        write_src(parsed_files)
        format_classes
        write_app_data
        write_classes
        @guides.write(@opts.output_dir+"/guides")
        @videos.write(@opts.output_dir+"/videos")
        @examples.write(@opts.output_dir+"/examples")
        @images.copy(@opts.output_dir+"/images")
      end

    end

    # Parses the files in parallel using as many processes as available CPU-s
@@ -132,7 +105,7 @@ module JsDuck

    # Formats each class
    def format_classes
      doc_formatter = get_doc_formatter
      doc_formatter = DocFormatter.new(@relations, @opts)
      doc_formatter.img_path = "images"
      class_formatter = ClassFormatter.new(@relations, doc_formatter)
      # Don't format types when exporting
@@ -147,130 +120,10 @@ module JsDuck
      # Then merge the data back to classes sequentially
      formatted_classes.each do |cls|
        @relations[cls[:doc][:name]].internal_doc = cls[:doc]
        cls[:images].each {|img| @images.add(img) }
      end
    end

    # Writes classes, guides, videos, and search data to one big .js file
    def write_app_data
      js = "Docs.data = " + JsonDuck.generate({
        :classes => Icons.new.create(@relations.classes),
        :guides => @guides.to_array,
        :videos => @videos.to_array,
        :examples => @examples.to_array,
        :search => SearchData.new.create(@relations.classes),
        :stats => @opts.stats ? Stats.new.create(@relations.classes) : [],
      }) + ";\n"
      File.open(@opts.output_dir+"/data.js", 'w') {|f| f.write(js) }
    end

    # Writes JSON export or JsonP file for each class
    def write_classes
      exporter = Exporter.new(@relations)
      renderer = Renderer.new
      # Inject formatter to all meta-tags.
      doc_formatter = get_doc_formatter
      @opts.meta_tags.each {|tag| tag.formatter = doc_formatter }
      renderer.meta_tags = @opts.meta_tags

      dir = @opts.output_dir + (@opts.export ? "" : "/output")
      @parallel.each(@relations.classes) do |cls|
        filename = dir + "/" + cls[:name] + (@opts.export ? ".json" : ".js")
        Logger.instance.log("Writing docs", filename)
        data = exporter.export(cls)
        if @opts.export
          JsonDuck.write_json(filename, data)
        else
          data[:html] = renderer.render(data)
          data = exporter.compact(data)
          JsonDuck.write_jsonp(filename, cls[:name].gsub(/\./, "_"), data)
        end
      end
    end

    # Writes formatted HTML source code for each input file
    def write_src(parsed_files)
      src = SourceWriter.new(@opts.output_dir + "/source", @opts.export ? nil : :page)
      # Can't be done in parallel, because file.html_filename= method
      # updates all the doc-objects related to the file
      parsed_files.each do |file|
        html_filename = src.write(file.to_html, file.filename)
        Logger.instance.log("Writing source", html_filename)
        file.html_filename = File.basename(html_filename)
        cls[:images].each {|img| @resources[:images].add(img) }
      end
    end

    # Creates and initializes DocFormatter
    def get_doc_formatter
      formatter = DocFormatter.new
      formatter.link_tpl = @opts.link_tpl if @opts.link_tpl
      formatter.img_tpl = @opts.img_tpl if @opts.img_tpl
      formatter.relations = @relations
      formatter
    end

    def copy_template
      Logger.instance.log("Copying template files to", @opts.output_dir)
      FileUtils.cp_r(@opts.template_dir, @opts.output_dir)
      init_output_dirs
    end

    def link_template
      Logger.instance.log("Linking template files to", @opts.output_dir)
      FileUtils.mkdir(@opts.output_dir)
      Dir.glob(@opts.template_dir + "/*").each do |file|
        File.symlink(File.expand_path(file), @opts.output_dir+"/"+File.basename(file))
      end
      init_output_dirs
    end

    def clear_output_dir
      if File.exists?(@opts.output_dir)
        FileUtils.rm_r(@opts.output_dir)
      end
    end

    def init_output_dirs
      FileUtils.mkdir(@opts.output_dir + "/output")
      FileUtils.mkdir(@opts.output_dir + "/source")
    end

    def create_template_html
      write_template("template.html", {
        "{title}" => @opts.title,
        "{header}" => @opts.header,
        "{footer}" => "<div id='footer-content' style='display: none'>#{@opts.footer}</div>",
        "{extjs_path}" => @opts.extjs_path,
        "{local_storage_db}" => @opts.local_storage_db,
        "{show_print_button}" => @opts.seo ? "true" : "false",
        "{touch_examples_ui}" => @opts.touch_examples_ui ? "true" : "false",
        "{welcome}" => @welcome.to_html,
        "{categories}" => @categories.to_html,
        "{guides}" => @guides.to_html,
        "{head_html}" => @opts.head_html,
        "{body_html}" => @opts.body_html,
      })
    end

    def create_print_template_html
      write_template("print-template.html", {
        "{title}" => @opts.title,
        "{header}" => @opts.header,
      })
    end

    # Opens file in template dir, replaces {keys} inside it, writes to output dir
    def write_template(filename, replacements)
      in_file = @opts.template_dir + '/' + filename
      out_file = @opts.output_dir + '/' + filename
      Logger.instance.log("Writing", out_file)
      html = IO.read(in_file)
      html.gsub!(/\{\w+\}/) do |key|
        replacements[key] ? replacements[key] : key
      end
      FileUtils.rm(out_file)
      File.open(out_file, 'w') {|f| f.write(html) }
    end
  end

end
+43 −0
Original line number Diff line number Diff line
require 'jsduck/parallel_wrap'
require 'jsduck/exporter'
require 'jsduck/renderer'
require 'jsduck/doc_formatter'
require 'jsduck/logger'
require 'jsduck/json_duck'

module JsDuck

  # Writes class JSON into files
  class ClassWriter
    def initialize(relations, opts)
      @relations = relations
      @opts = opts
      @parallel = ParallelWrap.new(:in_processes => @opts.processes)
    end

    # Writes JSON export or JsonP file for each class
    def write(dir, extension=".json")
      exporter = Exporter.new(@relations)
      renderer = Renderer.new
      # Inject formatter to all meta-tags.
      doc_formatter = DocFormatter.new(@relations, @opts)
      @opts.meta_tags.each {|tag| tag.formatter = doc_formatter }
      renderer.meta_tags = @opts.meta_tags

      @parallel.each(@relations.classes) do |cls|
        filename = dir + "/" + cls[:name] + extension
        Logger.instance.log("Writing docs", filename)
        data = exporter.export(cls)
        if extension == ".json"
          JsonDuck.write_json(filename, data)
        else
          data[:html] = renderer.render(data)
          data = exporter.compact(data)
          JsonDuck.write_jsonp(filename, cls[:name].gsub(/\./, "_"), data)
        end
      end
    end

  end

end
+4 −4
Original line number Diff line number Diff line
@@ -55,14 +55,14 @@ module JsDuck
    # name actually exists.
    attr_accessor :relations

    def initialize
    def initialize(relations={}, opts={})
      @class_context = ""
      @doc_context = {}
      @max_length = 120
      @relations = {}
      @relations = relations
      @images = []
      @link_tpl = '<a href="%c%#%m">%a</a>'
      @img_tpl = '<img src="%u" alt="%a"/>'
      @link_tpl = opts[:link_tpl] || '<a href="%c%#%m">%a</a>'
      @img_tpl = opts[:img_tpl] || '<img src="%u" alt="%a"/>'
      @link_re = /\{@link\s+(\S*?)(?:\s+(.+?))?\}/m
      @img_re = /\{@img\s+(\S*?)(?:\s+(.+?))?\}/m
      @example_annotation_re = /<pre><code>\s*@example( +[^\n]*)?\s+/m
+139 −0
Original line number Diff line number Diff line
require 'jsduck/class_writer'
require 'jsduck/logger'
require 'jsduck/source_writer'
require 'jsduck/json_duck'
require 'jsduck/icons'
require 'jsduck/search_data'
require 'jsduck/stats'
require 'fileutils'

module JsDuck

  # Performs the standard JSDuck HTML output
  class HtmlExporter
    def initialize(relations, resources, opts)
      @relations = relations
      @resources = resources
      @opts = opts
    end

    def run(&format_classes)
      FileUtils.rm_rf(@opts.output_dir)

      if @opts.template_links
        link_template
      else
        copy_template
      end

      FileUtils.mkdir(@opts.output_dir + "/output")
      FileUtils.mkdir(@opts.output_dir + "/source")

      create_template_html
      create_print_template_html

      if !@opts.seo
        FileUtils.rm(@opts.output_dir+"/index.php")
        FileUtils.cp(@opts.output_dir+"/template.html", @opts.output_dir+"/index.html")
      end

      if @opts.eg_iframe
        FileUtils.rm(@opts.output_dir+"/eg-iframe.html")
        FileUtils.cp(@opts.eg_iframe, @opts.output_dir+"/eg-iframe.html")
      end

      # class-formatting is done in parallel which breaks the links
      # between source files and classes. Therefore it MUST to be done
      # after write_src which needs the links to work.
      write_src
      format_classes.call

      write_app_data

      cw = ClassWriter.new(@relations, @opts)
      cw.write(@opts.output_dir+"/output", ".js")

      @resources[:guides].write(@opts.output_dir+"/guides")
      @resources[:videos].write(@opts.output_dir+"/videos")
      @resources[:examples].write(@opts.output_dir+"/examples")
      @resources[:images].copy(@opts.output_dir+"/images")
    end

    def copy_template
      Logger.instance.log("Copying template files to", @opts.output_dir)
      FileUtils.cp_r(@opts.template_dir, @opts.output_dir)
    end

    def link_template
      Logger.instance.log("Linking template files to", @opts.output_dir)
      FileUtils.mkdir(@opts.output_dir)
      Dir.glob(@opts.template_dir + "/*").each do |file|
        File.symlink(File.expand_path(file), @opts.output_dir+"/"+File.basename(file))
      end
    end

    def create_template_html
      write_template("template.html", {
        "{title}" => @opts.title,
        "{header}" => @opts.header,
        "{footer}" => "<div id='footer-content' style='display: none'>#{@opts.footer}</div>",
        "{extjs_path}" => @opts.extjs_path,
        "{local_storage_db}" => @opts.local_storage_db,
        "{show_print_button}" => @opts.seo ? "true" : "false",
        "{touch_examples_ui}" => @opts.touch_examples_ui ? "true" : "false",
        "{welcome}" => @resources[:welcome].to_html,
        "{categories}" => @resources[:categories].to_html,
        "{guides}" => @resources[:guides].to_html,
        "{head_html}" => @opts.head_html,
        "{body_html}" => @opts.body_html,
      })
    end

    def create_print_template_html
      write_template("print-template.html", {
        "{title}" => @opts.title,
        "{header}" => @opts.header,
      })
    end

    # Opens file in template dir, replaces {keys} inside it, writes to output dir
    def write_template(filename, replacements)
      in_file = @opts.template_dir + '/' + filename
      out_file = @opts.output_dir + '/' + filename
      Logger.instance.log("Writing", out_file)
      html = IO.read(in_file)
      html.gsub!(/\{\w+\}/) do |key|
        replacements[key] ? replacements[key] : key
      end
      FileUtils.rm(out_file)
      File.open(out_file, 'w') {|f| f.write(html) }
    end

    # Writes formatted HTML source code for each input file
    def write_src
      src = SourceWriter.new(@opts.output_dir + "/source", @opts.export ? nil : :page)
      # Can't be done in parallel, because file.html_filename= method
      # updates all the doc-objects related to the file
      @resources[:parsed_files].each do |file|
        html_filename = src.write(file.to_html, file.filename)
        Logger.instance.log("Writing source", html_filename)
        file.html_filename = File.basename(html_filename)
      end
    end

    # Writes classes, guides, videos, and search data to one big .js file
    def write_app_data
      js = "Docs.data = " + JsonDuck.generate({
        :classes => Icons.new.create(@relations.classes),
        :guides => @resources[:guides].to_array,
        :videos => @resources[:videos].to_array,
        :examples => @resources[:examples].to_array,
        :search => SearchData.new.create(@relations.classes),
        :stats => @opts.stats ? Stats.new.create(@relations.classes) : [],
      }) + ";\n"
      File.open(@opts.output_dir+"/data.js", 'w') {|f| f.write(js) }
    end

  end

end
+22 −0
Original line number Diff line number Diff line
require 'jsduck/class_writer'
require 'fileutils'

module JsDuck

  # Performs JSON export of class data
  class JsonExporter
    def initialize(relations, opts)
      @relations = relations
      @opts = opts
    end

    def run(&format_classes)
      format_classes.call
      FileUtils.rm_rf(@opts.output_dir)
      FileUtils.mkdir(@opts.output_dir)
      cw = ClassWriter.new(@relations, @opts)
      cw.write(@opts.output_dir, ".json")
    end
  end

end
Loading