jsduck 4.6.1 → 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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