GitHub PullRequestReviewCommentEvent at 2014-04-09 18:56:07
Repository: watson
Actor: houndci
Payload: {"comment":{"url":"https://api.github.com/repos/strudelfish/watson/pulls/comments/11452935","id":11452935,"diff_hunk":"@@ -0,0 +1,430 @@\n+module Watson\n+  # Dir/File parser class\n+  # Contains all necessary methods to parse through files and directories\n+  # for specified tags and generate data structure containing found issues\n+  class Parser\n+\n+    # Include for debug_print\n+    include Watson\n+\n+    # [review] - Should this require be required higher up or fine here\n+    # Include for Digest::MD5.hexdigest used in issue creating\n+    require 'digest'\n+    require 'pp'\n+\n+    COMMENT_DEFINITIONS = {\n+        '.cpp'     => ['//', '/*'],        # C++\n+        '.cxx'     => ['//', '/*'],\n+        '.cc'      => ['//', '/*'],\n+        '.hpp'     => ['//', '/*'],\n+        '.hxx'     => ['//', '/*'],\n+        '.c'       => ['//', '/*'],        # C\n+        '.h'       => ['//', '/*'],\n+        '.java'    => ['//', '/*', '/**'], # Java\n+        '.class'   => ['//', '/*', '/**'],\n+        '.cs'      => ['//', '/*'],        # C#\n+        '.scss'    => ['//', '/*'],        # SASS SCSS\n+        '.sass'    => ['//', '/*'],        # SASS SCSS\n+        '.js'      => ['//', '/*'],        # JavaScript\n+        '.php'     => ['//', '/*', '#'],   # PHP\n+        '.m'       => ['//', '/*'],        # ObjectiveC\n+        '.mm'      => ['//', '/*'],\n+        '.go'      => ['//', '/*'],        # Go(lang)\n+        '.scala'   => ['//', '/*'],        # Scala\n+        '.erl'     => ['%%', '%'],         # Erlang\n+        '.f'       => ['!'],               # Fortran\n+        '.f90'     => ['!'],               # Fortran\n+        '.F'       => ['!'],               # Fortran\n+        '.F90'     => ['!'],               # Fortran\n+        '.hs'      => ['--'],              # Haskell\n+        '.sh'      => ['#'],               # Bash\n+        '.rb'      => ['#'],               # Ruby\n+        '.haml'    => ['-#'],              # Haml\n+        '.pl'      => ['#'],               # Perl\n+        '.pm'      => ['#'],\n+        '.t'       => ['#'],\n+        '.py'      => ['#'],               # Python\n+        '.coffee'  => ['#'],               # CoffeeScript\n+        '.zsh'     => ['#'],               # Zsh\n+        '.clj'     => [';;'],              # Clojure\n+        '.sql'     => ['---', '//', '#' ], # SQL and PL types\n+        '.lua'     => ['--', '--[['],      # Lua\n+        '.vim'     => ['\"'],               # VimL\n+        '.md'      => ['<!--'],            # Markdown\n+        '.html'    => ['<!--'],            # HTML\n+        '.el'      => [';'],               # Emacslisp\n+        '.sqf'     => ['//','/*'],         # SQF\n+        '.sqs'     => [';'],               # SQS\n+        '.d'       => ['//','/*'],         # D\n+        '.tex'     => ['%'],               # LaTex\n+        '.hbs'     => ['{{!--'],           # Handlebars\n+        '.twig'    => ['{#']               # Twig\n+    }.freeze\n+\n+\n+    ###########################################################\n+    # Initialize the parser with the current watson config\n+    def initialize(config)\n+      # [review] - Not sure if passing config here is best way to access it\n+\n+      # Identify method entry\n+      debug_print \"#{ self } : #{ __method__ }\\n\"\n+\n+      @config = config\n+    end\n+\n+\n+    ###########################################################\n+    # Begins parsing of files / dirs specified in the initial dir/file lists\n+    def run\n+\n+      # Identify method entry\n+      debug_print \"#{ self } : #{ __method__ }\\n\"\n+\n+      # Go through all files added from CL (sort them first)\n+      # If empty, sort and each will do nothing, no errors\n+      _completed_dirs  = Array.new()\n+      _completed_files = Array.new()\n+      if @config.cl_entry_set\n+        @config.file_list.sort.each do |_file|\n+          _completed_files.push(parse_file(_file))\n+        end\n+      end\n+\n+      # Then go through all the specified directories\n+      # Initial parse depth to parse_dir is 0 (unlimited)\n+      @config.dir_list.sort.each do |_dir|\n+        _completed_dirs.push(parse_dir(_dir, 0))\n+      end\n+\n+      # Create overall hash for parsed files\n+      _structure           = Hash.new()\n+      _structure[:files]   = _completed_files\n+      _structure[:subdirs] = _completed_dirs\n+\n+      debug_print \"_structure dump\\n\\n\"\n+      debug_print PP.pp(_structure, '')\n+      debug_print \"\\n\\n\"\n+\n+      # Pass structure to poster with count as 0\n+      Remote.post_structure(_structure, @config, 0)\n+\n+      _structure\n+    end\n+\n+\n+    ###########################################################\n+    # Parse through specified directory and find all subdirs and files\n+    def parse_dir(dir, depth)\n+\n+      # Identify method entry\n+      debug_print \"#{ self } : #{ __method__ }\\n\"\n+\n+      # Error check on input\n+      if Watson::FS.check_dir(dir)\n+        debug_print \"Opened #{ dir } for parsing\\n\"\n+      else\n+        print \"Unable to open #{ dir }, exiting\\n\"\n+        return false\n+      end\n+\n+      debug_print \"Parsing through all files/directories in #{ dir }\\n\"\n+\n+      # [review] - Shifted away from single Dir.glob loop to separate for dir/file\n+      #        This duplicates code but is much better for readability\n+      #        Not sure which is preferred?\n+\n+\n+      # Remove leading . or ./\n+      _glob_dir = dir.gsub(/^\\.(\\/?)/, '')\n+      debug_print \"_glob_dir: #{_glob_dir}\\n\"\n+\n+\n+      # Go through directory to find all files\n+      # Create new array to hold all parsed files\n+      _completed_files = Array.new()\n+      Dir.glob(\"#{ _glob_dir }{*,.*}\").select { |_fn| File.file?(_fn) }.sort.each do |_entry|\n+        debug_print \"Entry: #{_entry} is a file\\n\"\n+\n+\n+        # [review] - Warning to user when file is ignored? (outside of debug_print)\n+        # Check against ignore list, if match, set to \"\" which will be ignored\n+        @config.ignore_list.each do |_ignore|\n+          if _mtch = _entry.match(_ignore)\n+            _entry = ''\n+            break\n+          end\n+        end\n+\n+        # If the resulting entry (after filtering) isn't empty, parse it and push into file array\n+        unless _entry.empty?\n+          debug_print \"Parsing #{ _entry }\\n\"\n+          _completed_files.push(parse_file(_entry))\n+        end\n+\n+      end\n+\n+\n+      # Go through directory to find all subdirs\n+      # Create new array to hold all parsed subdirs\n+      _completed_dirs = Array.new()\n+      Dir.glob(\"#{ _glob_dir }{*, .*}\").select { |_fn| File.directory?(_fn) }.sort.each do |_entry|\n+        debug_print \"Entry: #{ _entry } is a dir\\n\"\n+\n+        # Check if entry is in ignore list\n+        _skip = false\n+\n+        @config.ignore_list.each do |_ignore|\n+          if mtch = _entry.match(_ignore)\n+            _skip = true\n+          end\n+        end\n+\n+        debug_print \"#{ _entry } was not on ignorelist, adding\\n\"\n+\n+        # If directory is on the ignore list then skip\n+        next if _skip == true\n+\n+        ## Depth limit logic\n+        # Current depth is depth of previous parse_dir (passed in as second param) + 1\n+        _cur_depth = depth + 1\n+        debug_print \"Current Folder depth: #{ _cur_depth }\\n\"\n+\n+        # If Config.parse_depth is 0, no limit on subdir parsing\n+        if @config.parse_depth == 0\n+          debug_print \"No max depth, parsing directory\\n\"\n+          _completed_dirs.push(parse_dir(\"#{ _entry }/\", _cur_depth))\n+\n+        # If current depth is less than limit (set in config), parse directory and pass depth\n+        elsif _cur_depth < @config.parse_depth.to_i + 1\n+          debug_print \"Depth less than max dept (from config), parsing directory\\n\"\n+          _completed_dirs.push(parse_dir(\"#{ _entry }/\", _cur_depth))\n+\n+        # Else, depth is greater than limit, ignore the directory\n+        else\n+          debug_print \"Depth greater than max depth, ignoring\\n\"\n+        end\n+\n+        # Add directory to ignore list so it isn't repeated again accidentally\n+        @config.ignore_list.push(_entry)\n+      end\n+\n+\n+      # [review] - Not sure if Dir.glob requires a explicit directory/file close?\n+\n+      # Create hash to hold all parsed files and directories\n+      _structure           = Hash.new()\n+      _structure[:curdir]  = dir\n+      _structure[:files]   = _completed_files\n+      _structure[:subdirs] = _completed_dirs\n+      _structure\n+    end\n+\n+\n+    ###########################################################\n+    # Parse through individual files looking for issue tags, then generate formatted issue hash\n+    #noinspection RubyResolve\n+    def parse_file(filename)\n+      # [review] - Rename method input param to filename (more verbose?)\n+\n+      # Identify method entry\n+      debug_print \"#{ self } : #{ __method__ }\\n\"\n+\n+      _relative_path = filename\n+      _absolute_path = File.absolute_path(filename)\n+\n+      # Error check on input, use input filename to make sure relative path is correct\n+      if Watson::FS.check_file(_relative_path)\n+        debug_print \"Opened #{ _relative_path } for parsing\\n\"\n+        debug_print \"Short path: #{ _relative_path }\\n\"\n+      else\n+        print \"Unable to open #{ _relative_path }, exiting\\n\"\n+        return false\n+      end\n+\n+\n+      # Get filetype and set corresponding comment type\n+      _comment_type = get_comment_type(_relative_path)\n+      unless _comment_type\n+        debug_print \"Using default (#) comment type\\n\"\n+        _comment_type = ['#']\n+      end\n+\n+      # Escape out comment type for safety\n+      # [review] - Is there a way to do inplace join?\n+      _comment_type = _comment_type.map { |comment| Regexp.escape(comment) }.join(\"|\")\n+      debug_print \"Comment type #{ _comment_type }\\n\"\n+\n+      # [review] - It is possible to embed the valid tags in the regexp,\n+      # with a ~5% performance gain, but this would loose the warning about\n+      # unrecognized tags.\n+      _tag_format = Regexp.escape(@config.tag_format).gsub('\\\\ ', ' ')\n+      _tag_format_regex = _tag_format\n+                              .gsub(\"TAG\", '(\\w+)')\n+                              .gsub(\"COMMENT\", '(.+)')\n+                              .gsub(' ' , '\\s+')\n+\n+      _comment_regex = /^(?:\\s*[#{_comment_type}]+\\s*)+#{_tag_format_regex}/\n+\n+      debug_print \"Comment regex: #{_comment_regex}\\n\"\n+\n+      # Open file and read in entire thing into an array\n+      # Use an array so we can look ahead when creating issues later\n+      # [review] - Better var name than data for read in file?\n+      _data = File.read(_absolute_path).encode('UTF-8', :invalid => :replace).lines\n+\n+      # Initialize issue list hash\n+      _issue_list = Hash.new()\n+      _issue_list[:relative_path] = _relative_path\n+      _issue_list[:absolute_path] = _absolute_path\n+      _issue_list[:has_issues] = false\n+      @config.tag_list.each do | _tag |\n+        debug_print \"Creating array named #{ _tag }\\n\"\n+        # [review] - Use to_sym to make tag into symbol instead of string?\n+        _issue_list[_tag] = Array.new\n+      end\n+\n+      # Loop through all array elements (lines in file) and look for issues\n+      _data.each_with_index do |_line, _i|\n+\n+        # Find any comment line with [tag] - text (any comb of space and # acceptable)\n+        # Using if match to stay consistent (with config.rb) see there for\n+        # explanation of why I do this (not a good good one persay...)\n+        begin\n+          _mtch = _line.match(_comment_regex)\n+        rescue ArgumentError\n+          debug_print \"Could not encode to UTF-8, non-text\\n\"\n+        end\n+\n+        unless _mtch\n+          # debug_print \"No valid tag found in line, skipping\\n\"\n+          next\n+        end\n+\n+        # Set tag\n+        _tag = _mtch[1].downcase\n+\n+        # Make sure that the tag that was found is something we accept\n+        # If not, skip it but tell user about an unrecognized tag\n+        unless @config.tag_list.include?(_tag)\n+          formatter = Printer.new(@config).build_formatter\n+          formatter.print_status \"+\", GREEN\n+          print \"Unknown tag [#{ _tag }] found, ignoring\\n\"\n+          print \"      You might want to include it in your RC or with the -t/--tags flag\\n\"\n+          next\n+        end\n+\n+        # Found a valid match (with recognized tag)\n+        # Set flag for this issue_list (for file) to indicate that\n+        _issue_list[:has_issues] = true\n+\n+        # [review] - This could probably be done better, elsewhere!\n+        # If it's a HTML or Handlebars comment, remove trailing -->, --}}\n+        if _mtch[0].match(/[<{]+(!--)?(#)?/)\n+          _title = _mtch[2].gsub(/(--)?(#)?[>}]+/, \"\")\n+        else\n+          _title = _mtch[2]\n+        end\n+        debug_print \"Issue found\\n\"\n+        debug_print \"Tag: #{ _tag }\\n\"\n+        debug_print \"Issue: #{ _title }\\n\"\n+\n+        # Create hash for each issue found\n+        _issue               = Hash.new\n+        _issue[:line_number] = _i + 1\n+        _issue[:title]       = _title\n+\n+        # Grab context of issue specified by Config param (+1 to include issue itself)\n+        _context             = _data[_i..(_i + @config.context_depth + 1)]\n+\n+        # [review] - There has got to be a better way to do this...\n+        # Go through each line of context and determine indentation\n+        # Used to preserve indentation in post\n+        _cut                 = Array.new\n+        _context.each do |_line_sub|\n+          _max = 0\n+          # Until we reach a non indent OR the line is empty, keep slicin'\n+          until !_line_sub.match(/^( |\\t|\\n)/) || _line_sub.empty?\n+            # [fix] - Replace with inplace slice!\n+            _line_sub = _line_sub.slice(1..-1)\n+            _max      = _max + 1\n+\n+            debug_print \"New line: #{ _line_sub }\\n\"\n+            debug_print \"Max indent: #{ _max }\\n\"\n+          end\n+\n+          # Push max indent for current line to the _cut array\n+          _cut.push(_max)\n+        end\n+\n+        # Print old _context\n+        debug_print \"\\n\\n Old Context \\n\"\n+        debug_print PP.pp(_context, '')\n+        debug_print \"\\n\\n\"\n+\n+        # Trim the context lines to be left aligned but maintain indentation\n+        # Then add a single \\t to the beginning so the Markdown is pretty on GitHub/Bitbucket\n+        _context.map! { |_line_sub| \"\\t#{ _line_sub.slice(_cut.min .. -1) }\" }\n+\n+        # Print new _context\n+        debug_print(\"\\n\\n New Context \\n\")\n+        debug_print PP.pp(_context, '')\n+        debug_print(\"\\n\\n\")\n+\n+        _issue[:context] = _context\n+\n+        # These are accessible from _issue_list, but we pass individual issues\n+        # to the remote poster, so we need this here to reference them for GitHub/Bitbucket\n+        _issue[:tag]     = _tag\n+        _issue[:path]    = _relative_path\n+\n+        # Generate md5 hash for each specific issue (for bookkeeping)\n+        _issue[:md5]     = ::Digest::MD5.hexdigest(\"#{ _tag }, #{ _relative_path }, #{ _title }\")\n+        debug_print \"#{ _issue }\\n\"\n+\n+\n+        # [todo] - Figure out a way to queue up posts so user has a progress bar?\n+        # That way user can tell that wait is because of http calls not app\n+\n+        # If GitHub is valid, pass _issue to GitHub poster function\n+        # [review] - Keep Remote as a static method and pass config every time?\n+        #       Or convert to a regular class and make an instance with @config\n+\n+\n+        # [review] - Use _tag string as symbol reference in hash or keep as string?\n+        # Look into to_sym to keep format of all _issue params the same\n+        _issue_list[_tag].push(_issue)\n+\n+        # Increment issue counter for posting status\n+        @config.issue_count = @config.issue_count.next\n+      end\n+\n+      # [review] - Return of parse_file is different than watson-perl\n+      # Not sure which makes more sense, ruby version seems simpler\n+      # perl version might have to stay since hash scoping is weird in perl\n+      debug_print \"\\nIssue list: #{ _issue_list }\\n\"\n+\n+      _issue_list\n+    end\n+\n+\n+    ###########################################################\n+    # Get comment syntax for given file\n+    def get_comment_type(filename)\n+\n+      # Identify method entry\n+      debug_print \"#{ self } : #{ __method__ }\\n\"\n+\n+      # Merge config file type list with defaults\n+      _comments = COMMENT_DEFINITIONS.merge(@config.type_list)\n+\n+      # Grab all possible extensions, check for comment match in reverse order\n+      # Return comment type if found in comment definitions, else false\n+      filename.split('.')[1..-1].each { |_ext| return _comments['.' << _ext] if _comments.has_key?('.' << _ext) }","path":"lib/watson/parser.rb","position":423,"original_position":423,"commit_id":"2eb0906071046d4d8fd63cbfc58ecc18de0378f4","original_commit_id":"2eb0906071046d4d8fd63cbfc58ecc18de0378f4","user":{"login":"houndci","id":6697940,"avatar_url":"https://avatars.githubusercontent.com/u/6697940?","gravatar_id":"38d9cefd9971a05bf447f07c6cd332cc","url":"https://api.github.com/users/houndci","html_url":"https://github.com/houndci","followers_url":"https://api.github.com/users/houndci/followers","following_url":"https://api.github.com/users/houndci/following{/other_user}","gists_url":"https://api.github.com/users/houndci/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci/subscriptions","organizations_url":"https://api.github.com/users/houndci/orgs","repos_url":"https://api.github.com/users/houndci/repos","events_url":"https://api.github.com/users/houndci/events{/privacy}","received_events_url":"https://api.github.com/users/houndci/received_events","type":"User","site_admin":false},"body":"Line is too long. [113/80]","created_at":"2014-04-09T18:56:07Z","updated_at":"2014-04-09T18:56:07Z","html_url":"https://github.com/strudelfish/watson/pull/1#discussion_r11452935","pull_request_url":"https://api.github.com/repos/strudelfish/watson/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/strudelfish/watson/pulls/comments/11452935"},"html":{"href":"https://github.com/strudelfish/watson/pull/1#discussion_r11452935"},"pull_request":{"href":"https://api.github.com/repos/strudelfish/watson/pulls/1"}}}}