jsduck 4.6.1 → 4.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,7 @@ require 'jsduck/util/null_object'
5
5
  require 'jsduck/logger'
6
6
  require 'jsduck/grouped_asset'
7
7
  require 'jsduck/util/html'
8
+ require 'jsduck/img/dir'
8
9
  require 'fileutils'
9
10
 
10
11
  module JsDuck
@@ -39,6 +40,7 @@ module JsDuck
39
40
  def load_all_guides
40
41
  each_item do |guide|
41
42
  guide["url"] = resolve_url(guide)
43
+ guide[:filename] = guide["url"] + "/README.md"
42
44
  guide[:html] = load_guide(guide)
43
45
  end
44
46
  end
@@ -52,22 +54,29 @@ module JsDuck
52
54
 
53
55
  def load_guide(guide)
54
56
  return Logger.warn(:guide, "Guide not found", guide["url"]) unless File.exists?(guide["url"])
55
-
56
- guide_file = guide["url"] + "/README.md"
57
-
58
- return Logger.warn(:guide, "Guide not found", guide_file) unless File.exists?(guide_file)
57
+ return Logger.warn(:guide, "Guide not found", guide[:filename]) unless File.exists?(guide[:filename])
59
58
 
60
59
  begin
61
- @formatter.doc_context = {:filename => guide_file, :linenr => 0}
62
- @formatter.img_path = "guides/#{guide["name"]}"
63
-
64
- return add_toc(guide, @formatter.format(Util::IO.read(guide_file)))
60
+ return format_guide(guide)
65
61
  rescue
66
62
  Logger.fatal_backtrace("Error while reading/formatting guide #{guide['url']}", $!)
67
63
  exit(1)
68
64
  end
69
65
  end
70
66
 
67
+ def format_guide(guide)
68
+ @formatter.doc_context = {:filename => guide[:filename], :linenr => 0}
69
+ @formatter.images = Img::Dir.new(guide["url"], "guides/#{guide["name"]}")
70
+ html = add_toc(guide, @formatter.format(Util::IO.read(guide[:filename])))
71
+
72
+ # Report unused images (but ignore the icon files)
73
+ @formatter.images.get("icon.png")
74
+ @formatter.images.get("icon-lg.png")
75
+ @formatter.images.report_unused
76
+
77
+ return html
78
+ end
79
+
71
80
  def write_guide(guide, dir)
72
81
  return unless guide[:html]
73
82
 
@@ -4,18 +4,6 @@ module JsDuck
4
4
 
5
5
  # Tracks opening and closing of HTML tags, with the purpose of
6
6
  # closing down the unfinished tags.
7
- #
8
- # Synopsis:
9
- #
10
- # tags = HtmlStack.new
11
- # # open and close a bunch of tags
12
- # tags.open("a")
13
- # tags.open("b")
14
- # tags.close("b")
15
- #
16
- # # ask which tags still need to be closed
17
- # tags.close_unfinished --> "</a>"
18
- #
19
7
  class HtmlStack
20
8
 
21
9
  # Initializes the stack with two optional parameters:
@@ -28,18 +16,23 @@ module JsDuck
28
16
  @open_tags = []
29
17
  end
30
18
 
31
- # Registers opening of a tag. Returns the tag.
32
- def open(tag)
33
- @open_tags.unshift(tag) unless void?(tag)
34
- tag
19
+ # Scans an opening tag in HTML using the passed in StringScanner.
20
+ def open(s)
21
+ s.scan(/</) + push_tag(s.scan(/\w+/)) + s.scan_until(/>|\Z/)
35
22
  end
36
23
 
37
- # Registers closing of a tag. Returns the tag.
38
- def close(tag)
39
- if @open_tags.include?(tag)
40
- # delete the most recent unclosed tag in our tags stack
41
- @open_tags.delete_at(@open_tags.index(tag))
42
- end
24
+ # Scans a closing tag in HTML using the passed in StringScanner.
25
+ def close(s)
26
+ s.scan(/<\//)
27
+ tag = s.scan(/\w+/)
28
+ s.scan(/>/)
29
+
30
+ pop_tags(tag).map {|t| "</#{t}>" }.join
31
+ end
32
+
33
+ # Registers opening of a tag. Returns the tag.
34
+ def push_tag(tag)
35
+ @open_tags.push(tag) unless void?(tag)
43
36
  tag
44
37
  end
45
38
 
@@ -48,22 +41,43 @@ module JsDuck
48
41
  @open_tags.include?(tag)
49
42
  end
50
43
 
51
- # Returns HTML for closing the still open tags.
52
- # Also prints warnings for all the unclosed tags.
53
- def close_unfinished
54
- return "" if @open_tags.length == 0
44
+ private
45
+
46
+ # Registers closing of a tag. Returns all the tags that need to
47
+ # be closed at that point.
48
+ def pop_tags(tag)
49
+ if !@open_tags.include?(tag)
50
+ if @ignore_html[tag]
51
+ return [tag]
52
+ else
53
+ warn_unopened(tag)
54
+ return []
55
+ end
56
+ end
55
57
 
56
- warn_unfinished
58
+ popped = []
59
+ begin
60
+ popped << t = @open_tags.pop
61
+ if t != tag
62
+ warn_unclosed(t)
63
+ end
64
+ end until t == tag
57
65
 
58
- @open_tags.map {|tag| "</#{tag}>" }.join
66
+ popped
59
67
  end
60
68
 
61
- private
69
+ def warn_unopened(*tags)
70
+ warn("Unopened HTML tag", tags)
71
+ end
72
+
73
+ def warn_unclosed(*tags)
74
+ warn("Unclosed HTML tag", tags)
75
+ end
62
76
 
63
- def warn_unfinished
77
+ def warn(msg, tags)
64
78
  ctx = @doc_context
65
- tag_list = @open_tags.map {|tag| "<#{tag}>" }.join(", ")
66
- Logger.warn(:html, "Unclosed HTML tag: #{tag_list}", ctx[:filename], ctx[:linenr])
79
+ tag_list = tags.map {|tag| "<#{tag}>" }.join(", ")
80
+ Logger.warn(:html, "#{msg}: #{tag_list}", ctx[:filename], ctx[:linenr])
67
81
  end
68
82
 
69
83
  def void?(tag)
@@ -0,0 +1,94 @@
1
+ require 'dimensions'
2
+ require 'jsduck/logger'
3
+
4
+ module JsDuck
5
+ module Img
6
+
7
+ # Looks up images from a directory.
8
+ class Dir
9
+ def initialize(full_path, relative_path)
10
+ @full_path = full_path
11
+ @relative_path = relative_path
12
+ @images = {}
13
+ end
14
+
15
+ # Retrieves hash of information for a given relative image
16
+ # filename. It will have the fields:
17
+ #
18
+ # - :filename - the same as the parameter of this method
19
+ # - :full_path - actual path in the filesystem.
20
+ # - :relative_path - relative path to be used inside <img> tag.
21
+ # - :width - Image width
22
+ # - :height - Image height
23
+ #
24
+ # When the image is not found, returns nil.
25
+ def get(filename)
26
+ img = scan_img(filename)
27
+ if img
28
+ @images[filename] = img
29
+ end
30
+ img
31
+ end
32
+
33
+ # Returns all used images.
34
+ def all_used
35
+ @images.values
36
+ end
37
+
38
+ # Print warnings about all unused images.
39
+ def report_unused
40
+ scan_for_unused_images.each {|img| warn_unused(img) }
41
+ end
42
+
43
+ private
44
+
45
+ def scan_img(filename)
46
+ full_path = File.join(@full_path, filename)
47
+ if File.exists?(File.join(@full_path, filename))
48
+ img_record(filename)
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ # Scans directory for image files, building a hash of image files
55
+ # found in that directory.
56
+ def scan_for_unused_images
57
+ unused = []
58
+ ::Dir[@full_path+"/**/*.{png,jpg,jpeg,gif}"].each do |path|
59
+ filename = relative_path(@full_path, path)
60
+ unused << img_record(filename) unless @images[filename]
61
+ end
62
+ unused
63
+ end
64
+
65
+ def warn_unused(img)
66
+ Logger.warn(:image_unused, "Image not used.", img[:full_path])
67
+ end
68
+
69
+ def img_record(filename)
70
+ full_path = File.join(@full_path, filename)
71
+ width, height = Dimensions.dimensions(full_path)
72
+
73
+ return {
74
+ :filename => filename,
75
+ :relative_path => File.join(@relative_path, filename),
76
+ :full_path => full_path,
77
+ :width => width,
78
+ :height => height,
79
+ }
80
+ end
81
+
82
+ # Given a path to directory and a path to file, returns the path
83
+ # to this file relative to the given dir. For example:
84
+ #
85
+ # base_path("/foo/bar", "/foo/bar/baz/img.jpg") --> "baz/img.jpg"
86
+ #
87
+ def relative_path(dir_path, file_path)
88
+ file_path.slice(dir_path.length+1, file_path.length)
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,39 @@
1
+ require "jsduck/img/dir"
2
+ require "jsduck/logger"
3
+ require "fileutils"
4
+
5
+ module JsDuck
6
+ module Img
7
+
8
+ # A collection if Img::Dir objects.
9
+ #
10
+ # Looks up images from directories specified through --images
11
+ # option.
12
+ #
13
+ # This class provides the same interface as Img::Dir, except that
14
+ # the constructor takes array of full_paths not just one.
15
+ class DirSet
16
+ def initialize(full_paths, relative_path)
17
+ @dirs = full_paths.map {|path| Img::Dir.new(path, relative_path) }
18
+ end
19
+
20
+ def get(filename)
21
+ @dirs.each do |dir|
22
+ if img = dir.get(filename)
23
+ return img
24
+ end
25
+ end
26
+ return nil
27
+ end
28
+
29
+ def all_used
30
+ @dirs.map {|dir| dir.all_used }.flatten
31
+ end
32
+
33
+ def report_unused
34
+ @dirs.each {|dir| dir.report_unused }
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ require "jsduck/logger"
2
+ require "fileutils"
3
+
4
+ module JsDuck
5
+ module Img
6
+
7
+ # Copies images to destination directory.
8
+ class Writer
9
+ # Takes an array of image records retrieved from
10
+ # Img::Dir#all_used or Img::DirSet#all_used and copies all of
11
+ # them to given output directory.
12
+ def self.copy(images, output_dir)
13
+ images.each do |img|
14
+ dest = File.join(output_dir, img[:filename])
15
+ Logger.log("Copying image", dest)
16
+ FileUtils.makedirs(File.dirname(dest))
17
+ FileUtils.cp(img[:full_path], dest)
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,106 @@
1
+ require 'jsduck/logger'
2
+
3
+ module JsDuck
4
+ module Inline
5
+
6
+ # Takes care of the auto-detection of links in text.
7
+ class AutoLink
8
+ # Sets up instance to work in context of particular class, so it
9
+ # knows that #blah is in context of SomeClass.
10
+ attr_accessor :class_context
11
+
12
+ # Sets up instance to work in context of particular doc object.
13
+ # Used for error reporting.
14
+ attr_accessor :doc_context
15
+
16
+ def initialize(link_renderer)
17
+ @class_context = ""
18
+ @doc_context = {}
19
+ @relations = link_renderer.relations
20
+ @renderer = link_renderer
21
+ @magic_link_re = magic_link_re
22
+ end
23
+
24
+ # Looks input text for patterns like:
25
+ #
26
+ # My.ClassName
27
+ # MyClass#method
28
+ # #someProperty
29
+ #
30
+ # and converts them to links, as if they were surrounded with
31
+ # {@link} tag. One notable exception is that Foo is not created to
32
+ # link, even when Foo class exists, but Foo.Bar is. This is to
33
+ # avoid turning normal words into links. For example:
34
+ #
35
+ # Math involves a lot of numbers. Ext JS is a JavaScript framework.
36
+ #
37
+ # In these sentences we don't want to link "Math" and "Ext" to the
38
+ # corresponding JS classes. And that's why we auto-link only
39
+ # class names containing a dot "."
40
+ #
41
+ def replace(input)
42
+ input.gsub(@magic_link_re) do
43
+ cls = $1 || $3
44
+ member = $2 || $4
45
+ replace_magic_link(cls, member)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # Generates regex for auto-linking class and member names in text.
52
+ def magic_link_re
53
+ ident_re = "(?:[A-Za-z_$][A-Za-z0-9_$]*)"
54
+ cls_re = "(#{ident_re}(?:\\.#{ident_re})*)"
55
+ ns_cls_re = "(#{ident_re}(?:\\.#{ident_re})+)"
56
+ member_re = "(?:#(#{ident_re}))"
57
+ /#{cls_re}#{member_re}|#{ns_cls_re}|#{member_re}/m
58
+ end
59
+
60
+ def replace_magic_link(cls, member)
61
+ if cls && member
62
+ if @relations[cls] && @renderer.get_matching_member(cls, {:name => member})
63
+ return @renderer.link(cls, member, cls+"."+member)
64
+ else
65
+ warn_magic_link("#{cls}##{member} links to non-existing " + (@relations[cls] ? "member" : "class"))
66
+ end
67
+ elsif cls
68
+ if @relations[cls]
69
+ return @renderer.link(cls, nil, cls)
70
+ else
71
+ cls2, member2 = split_to_cls_and_member(cls)
72
+ if @relations[cls2] && @renderer.get_matching_member(cls2, {:name => member2})
73
+ return @renderer.link(cls2, member2, cls2+"."+member2)
74
+ elsif cls =~ /\.(js|css|html|php)\Z/
75
+ # Ignore common filenames
76
+ else
77
+ warn_magic_link("#{cls} links to non-existing class")
78
+ end
79
+ end
80
+ else
81
+ if @renderer.get_matching_member(@class_context, {:name => member})
82
+ return @renderer.link(@class_context, member, member)
83
+ elsif member =~ /\A([A-F0-9]{3}|[A-F0-9]{6})\Z/i || member =~ /\A[0-9]/
84
+ # Ignore HEX color codes and
85
+ # member names beginning with number
86
+ else
87
+ warn_magic_link("##{member} links to non-existing member")
88
+ end
89
+ end
90
+
91
+ return "#{cls}#{member ? '#' : ''}#{member}"
92
+ end
93
+
94
+ def split_to_cls_and_member(str)
95
+ parts = str.split(/\./)
96
+ return [parts.slice(0, parts.length-1).join("."), parts.last]
97
+ end
98
+
99
+ def warn_magic_link(msg)
100
+ Logger.warn(:link_auto, msg, @doc_context[:filename], @doc_context[:linenr])
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
@@ -1,25 +1,24 @@
1
1
  require 'jsduck/util/html'
2
2
  require 'jsduck/logger'
3
+ require 'pp'
3
4
 
4
5
  module JsDuck
5
6
  module Inline
6
7
 
7
8
  # Implementation of inline tag {@img}
8
9
  class Img
9
- # Base path to prefix images from {@img} tags.
10
- # Defaults to no prefix.
11
- attr_accessor :base_path
12
-
13
- # This will hold list of all image paths gathered from {@img} tags.
10
+ # Instance of Img::Dir or Img::DirSet that's used for looking up
11
+ # image information.
14
12
  attr_accessor :images
15
13
 
14
+ # Sets up instance to work in context of particular doc object.
15
+ # Used for error reporting.
16
+ attr_accessor :doc_context
17
+
16
18
  def initialize(opts={})
17
- @tpl = opts[:img_tpl] || '<img src="%u" alt="%a"/>'
19
+ @tpl = opts[:img_tpl] || '<img src="%u" alt="%a" width="%w" height="%h"/>'
18
20
 
19
21
  @re = /\{@img\s+(\S*?)(?:\s+(.+?))?\}/m
20
-
21
- @base_path = nil
22
- @images = []
23
22
  end
24
23
 
25
24
  # Takes StringScanner instance.
@@ -37,13 +36,22 @@ module JsDuck
37
36
 
38
37
  # applies the image template
39
38
  def apply_tpl(url, alt_text)
40
- @images << url
39
+ img = @images.get(url)
40
+ if !img
41
+ Logger.warn(:image, "Image #{url} not found.", @doc_context[:filename], @doc_context[:linenr])
42
+ img = {}
43
+ end
44
+
41
45
  @tpl.gsub(/(%\w)/) do
42
46
  case $1
43
47
  when '%u'
44
- @base_path ? (@base_path + "/" + url) : url
48
+ img[:relative_path]
45
49
  when '%a'
46
50
  Util::HTML.escape(alt_text||"")
51
+ when '%w'
52
+ img[:width]
53
+ when '%h'
54
+ img[:height]
47
55
  else
48
56
  $1
49
57
  end