{"action":"created","issue":{"url":"https://api.github.com/repos/d3/d3-zoom/issues/58","repository_url":"https://api.github.com/repos/d3/d3-zoom","labels_url":"https://api.github.com/repos/d3/d3-zoom/issues/58/labels{/name}","comments_url":"https://api.github.com/repos/d3/d3-zoom/issues/58/comments","events_url":"https://api.github.com/repos/d3/d3-zoom/issues/58/events","html_url":"https://github.com/d3/d3-zoom/issues/58","id":179026775,"node_id":"MDU6SXNzdWUxNzkwMjY3NzU=","number":58,"title":"Stale source links in readme reference wrong line","user":{"login":"mootari","id":322014,"node_id":"MDQ6VXNlcjMyMjAxNA==","avatar_url":"https://avatars2.githubusercontent.com/u/322014?v=4","gravatar_id":"","url":"https://api.github.com/users/mootari","html_url":"https://github.com/mootari","followers_url":"https://api.github.com/users/mootari/followers","following_url":"https://api.github.com/users/mootari/following{/other_user}","gists_url":"https://api.github.com/users/mootari/gists{/gist_id}","starred_url":"https://api.github.com/users/mootari/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mootari/subscriptions","organizations_url":"https://api.github.com/users/mootari/orgs","repos_url":"https://api.github.com/users/mootari/repos","events_url":"https://api.github.com/users/mootari/events{/privacy}","received_events_url":"https://api.github.com/users/mootari/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2016-09-24T11:57:50Z","updated_at":"2018-06-30T09:58:23Z","closed_at":"2016-09-24T12:10:37Z","author_association":"NONE","body":"Source links were added in [1ba39e7](https://github.com/d3/d3-zoom/commit/1ba39e74db6c46b3c644cefc7d674fa5e9ccd041) but reference the master branch. Because the code has changed since that commit many (if not all) source links now reference arbitrary lines.\n\nProposed solutions:\n1. Remove source links. It's not that hard to search the source, although the links provide a good incentive to actually look there.\n2. Link to the code search ([example for scaleExtent](https://github.com/d3/d3-zoom/search?utf8=%E2%9C%93&q=scaleExtent+filename%3Azoom.js&type=Code)). Quality of results might vary.\n3. Devise a process to automatically update source links.\n"},"comment":{"url":"https://api.github.com/repos/d3/d3-zoom/issues/comments/401530971","html_url":"https://github.com/d3/d3-zoom/issues/58#issuecomment-401530971","issue_url":"https://api.github.com/repos/d3/d3-zoom/issues/58","id":401530971,"node_id":"MDEyOklzc3VlQ29tbWVudDQwMTUzMDk3MQ==","user":{"login":"mootari","id":322014,"node_id":"MDQ6VXNlcjMyMjAxNA==","avatar_url":"https://avatars2.githubusercontent.com/u/322014?v=4","gravatar_id":"","url":"https://api.github.com/users/mootari","html_url":"https://github.com/mootari","followers_url":"https://api.github.com/users/mootari/followers","following_url":"https://api.github.com/users/mootari/following{/other_user}","gists_url":"https://api.github.com/users/mootari/gists{/gist_id}","starred_url":"https://api.github.com/users/mootari/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mootari/subscriptions","organizations_url":"https://api.github.com/users/mootari/orgs","repos_url":"https://api.github.com/users/mootari/repos","events_url":"https://api.github.com/users/mootari/events{/privacy}","received_events_url":"https://api.github.com/users/mootari/received_events","type":"User","site_admin":false},"created_at":"2018-06-30T09:58:23Z","updated_at":"2018-06-30T09:58:23Z","author_association":"NONE","body":"During some (late) spring data cleaning I rediscovered code I had already written that tracks line positions across commits and dumps changes to stdout. Dropping the code here for reference.\r\n\r\nExample output (excerpt):\r\n```\r\nSource                                               | Old | New\r\n==================================================== | === | ===\r\n  zoom.transform = function(collection, transform) { | 62  | 84 \r\n  zoom.transform = function(collection, transform) { | 76  | 84 \r\n  zoom.translateBy = function(selection, x, y) {     | 110 | 118\r\n  zoom.translateTo = function(selection, x, y) {     | 119 | 127\r\n  zoom.scaleBy = function(selection, k) {            | 91  | 99 \r\n  zoom.scaleTo = function(selection, k) {            | 99  | 107\r\n```\r\nNode script:\r\n```javascript\r\nvar fs = require('fs');\r\nvar execSync = require('child_process').execSync;\r\nvar readline = require('readline');\r\n\r\noptions = {\r\n  dir: 'd3-zoom',\r\n  file: 'README.md',\r\n  prefix: 'https://github.com/d3/d3-zoom/blob/master/',\r\n  delimiter: '#L',\r\n  suffix: ' ',\r\n  updateFile: false\r\n};\r\n\r\nfunction findSourceLinks(data, prefix, delimiter, suffix) {\r\n  function esc(str) {\r\n    return str.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\");\r\n  }\r\n  var pattern, lines, matches = [];\r\n\r\n  pattern = new RegExp(esc(prefix) + '(.+?)' + esc(delimiter) + '([0-9]+)' + esc(suffix), 'g');\r\n  lines = data.split('\\n');\r\n\r\n  lines.forEach(function(str, index) {\r\n    var match;\r\n    pattern.lastIndex = 0;\r\n    while(match = pattern.exec(str)) {\r\n      matches.push({\r\n        lineContent: str,\r\n        lineIndex: index,\r\n        linkIndex: match.index,\r\n        targetUrl: match[0],\r\n        targetFile: match[1],\r\n        targetLine: +match[2]\r\n      });\r\n    }\r\n  });\r\n\r\n  return matches;\r\n}\r\n\r\n/**\r\n * Finds the first commit that introduced a string into a file.\r\n *\r\n * @param {string} gitCmd - Base git command.\r\n * @param {string} str - Search string.\r\n * @param {string} file - File path.\r\n * @returns {string}|{null} First commit ID or null if none was found.\r\n */\r\nfunction gitStringOrigin(gitCmd, str, file) {\r\n  var result, match;\r\n\r\n  result = execSync(gitCmd + ' log --reverse --oneline -S \"' + str + '\" -- \"' + file + '\"');\r\n  match = result.toString().match(/^[a-f0-9]{5,}/);\r\n  return match ? match[0] : null;\r\n}\r\n\r\n/**\r\n * Returns the content of a file's line in a specific commit.\r\n *\r\n * @param {string} gitCmd - Base git command.\r\n * @param {string} commit - Commit ID.\r\n * @param {string} file - File path.\r\n * @param {string} line - Line number.\r\n * @returns {string}|{null} - Either the line content or null if not found.\r\n */\r\nfunction stringGitSource(gitCmd, commit, file, line) {\r\n  var result, lines;\r\n\r\n  result = execSync(gitCmd + ' show ' + commit + ':\"' + file + '\"');\r\n  lines = result.toString().split('\\n');\r\n  return lines.length >= line ? lines[line - 1] : null;\r\n}\r\n\r\n/**\r\n * Renders a formatted table.\r\n * @param {string[]} headers - Header labels.\r\n * @param {string[][]} rows - Row data.\r\n * @returns {string} - Rendered table.\r\n */\r\nfunction renderTable(headers, rows) {\r\n  /**\r\n   * Returns column widths for a set of rows.\r\n   * @param {string[][]} rows - Rows with column data.\r\n   * @returns {number[]} - Maximum width of each column.\r\n   */\r\n  function colWidths(rows) {\r\n    return rows.reduce(function(sizes, row) {\r\n      return row.map(function(str, i) {\r\n        str += '';\r\n        return sizes[i] && sizes[i] > str.length ? sizes[i] : str.length;\r\n      });\r\n    }, []);\r\n  }\r\n\r\n  /**\r\n   * Extends a string to a given length.\r\n   * @param {string} str\r\n   * @param {number} length - Target length.\r\n   * @param {string} char - Character to append.\r\n   * @returns {string}\r\n   */\r\n  function fill(str, length, char) {\r\n    str += '';\r\n    while(str.length < length) {\r\n      str += char;\r\n    }\r\n    return str;\r\n  }\r\n\r\n  /**\r\n   * Renders a single row.\r\n   * @param {number[]} widths - Column sizes.\r\n   * @param {string[]} items - Column data.\r\n   * @param {string} fillChar - Character to fill up short columns with.\r\n   * @param {string} delimiter - Column delimiter.\r\n   * @returns {string} - Rendered column.\r\n   */\r\n  function renderRow(widths, items, fillChar, delimiter) {\r\n    return items.map(function(str, i) {\r\n      return fill(str, widths[i], fillChar);\r\n    }).join(delimiter);\r\n  };\r\n\r\n  var widths = colWidths([headers].concat(rows));\r\n  var output = [];\r\n  output.push(renderRow(widths, headers, ' ', ' | '));\r\n  output.push(renderRow(widths, headers.fill(''), '=', ' | '));\r\n  rows.forEach(function(row) {\r\n    output.push(renderRow(widths, row, ' ', ' | '));\r\n  });\r\n\r\n  return output.join('\\n');\r\n}\r\n\r\ntargetFile = options.dir + '/' + options.file;\r\n\r\nvar reader = readline.createInterface({\r\n  input: fs.createReadStream(targetFile)\r\n});\r\nreader.on('line', function(data) {\r\n\r\n});\r\n\r\nfs.readFile(targetFile, 'UTF-8', function(err, data) {\r\n  var matches, lines, cmd, changes = [];\r\n\r\n  matches = findSourceLinks(data, options.prefix, options.delimiter, options.suffix);\r\n  cmd = 'git -C \"' + options.dir + '\"';\r\n  matches.forEach(function(match) {\r\n    var commit = gitStringOrigin(cmd, match.targetUrl, options.file);\r\n    if(commit === null) {\r\n      return;\r\n    }\r\n    match.linkCommit = commit;\r\n    match.targetSource = stringGitSource(cmd, match.linkCommit, match.targetFile, match.targetLine);\r\n\r\n    filedata = fs.readFileSync(options.dir + '/' + match.targetFile).toString().split('\\n');\r\n    filedata.some(function(line, index) {\r\n      if(line.indexOf(match.targetSource) >= 0) {\r\n        match.targetLineNew = index + 1;\r\n        return true;\r\n      }\r\n    });\r\n  });\r\n\r\n  lines = data.split('\\n');\r\n  matches.forEach(function(match) {\r\n    var parts, updated;\r\n    if(match.targetLineNew !== match.targetLine) {\r\n      parts = match.targetUrl.split(options.delimiter, 2);\r\n      updated = parts[0] + options.delimiter + match.targetLineNew + options.suffix;\r\n      lines[match.lineIndex] = lines[match.lineIndex].replace(match.targetUrl, updated);\r\n      changes.push([\r\n        match.targetSource,\r\n        match.targetLine,\r\n        match.targetLineNew\r\n      ]);\r\n    }\r\n  });\r\n\r\n  if(changes.length) {\r\n    if(options.updateFile) {\r\n      console.log('Updating ' + targetFile);\r\n      fs.writeFile(targetFile, lines.join('\\n'), function(err) {\r\n        if(err) {\r\n          console.log(err);\r\n        }\r\n      });\r\n    }\r\n    console.log(renderTable(['Source', 'Old', 'New'], changes));\r\n  }\r\n  else {\r\n    console.log('No changes found.');\r\n  }\r\n\r\n});\r\n```"}}
{"actor":{"display_login":"mootari"}}