Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #12 where man page would not be present after package install using npm #20

Closed
wants to merge 1 commit into from

Conversation

psyrendust
Copy link
Contributor

Resolves issues presented npm/npm#3405 and npm/npm#109.

The secret sauce was found with a little trial and error after reading this article Unix and Node: Manual Pages - Installing Manual Pages. It wasn't very clear at first, but the key was to point to the folder where the roff file lives and not the path to the roff file:

"directories": {
  "man": "<folder_to_manpage>"
}

So the following are incorrect:

"bin": "bin/luamin",
"man": "man/luamin.1",
...
"bin": "bin/luamin",
"man": "man/luamin.1",
"directories": {
  "man": "man/luamin.1"
},
...
"bin": "bin/luamin",
"directories": {
  "man": "man/luamin.1"
},
...

This is the correct way:

  • No man key at the root level
  • A directories entry with a man key that has a value which points to the folder where the roff file lives
"bin": "bin/luamin",
"directories": {
  "man": "man"
}

@mathiasbynens
Copy link
Owner

Thanks for the effort you put in to this!

Just to be clear, this is a workaround for those npm bugs, right? Or are you saying there is no bug in npm and I was just doing it wrong? (In that case I misread https://www.npmjs.org/doc/json.html#man…)

@psyrendust
Copy link
Contributor Author

Ok, so you got me digging through npm source now :)

TL;DR

Nope it's not a hack.

Long winded answer

So it turns out that the docs for package.json.md#man might work, but I couldn't seem to get it to work as stated.

But the interesting piece is the following package.json.md#directories:

The CommonJS Packages spec details a few ways that you can indicate the structure of your package using a directories hash. If you look at npm's package.json, you'll see that it has directories for doc, lib, and man.

In the future, this information may be used in other creative ways.

and...

directories.man

A folder that is full of man pages. Sugar to generate a "man" array by walking the folder.

Oh how I love sugar!

And here npm-developers.md#the-packagejson-file:

  • directories: This is a hash of folders. The best ones to include are "lib" and "doc", but if you specify a folder full of man pages in "man", then they'll get installed just like these ones.

So here's the logic for the linkMan function in build.js:

function linkMans (pkg, folder, parent, gtop, cb) {
  if (!pkg.man || !gtop || process.platform === "win32") return cb()

  var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")

  // make sure that the mans are unique.
  // otherwise, if there are dupes, it'll fail with EEXIST
  var set = pkg.man.reduce(function (acc, man) {
    acc[path.basename(man)] = man
    return acc
  }, {})
  pkg.man = pkg.man.filter(function (man) {
    return set[path.basename(man)] === man
  })

  asyncMap(pkg.man, function (man, cb) {
    if (typeof man !== "string") return cb()
    var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
      , stem = parseMan[1]
      , sxn = parseMan[2]
      , gz = parseMan[3] || ""
      , bn = path.basename(stem)
      , manDest = path.join(manRoot, "man" + sxn, bn)

    linkIfExists(man, manDest, gtop && folder, cb)
  }, cb)
}

Which made me think that pkg.man was the thing to set, but after a little digging I found the following in 2 functions mans and mans_ in read-json.js:

function mans (file, data, cb) {
                var m = data.directories && data.directories.man
                if (data.man || !m) return cb(null, data);
                m = path.resolve(path.dirname(file), m)
                glob("**/*.[0-9]", { cwd: m }, function (er, mans) {
                                if (er) return cb(er);
                                mans_(file, data, mans, cb)
                })
}
function mans_ (file, data, mans, cb) {
                var m = data.directories && data.directories.man
                data.man = mans.map(function (mf) {
                                return path.resolve(path.dirname(file), m, mf)
                })
                return cb(null, data)
}

These two functions will scrape the directories.man folder for file names that match the pattern *.[1-9] (eg luamin.1). It then sets data.man to an array of files and returns data with that property set. So now when buils.js runs, it loads the packages package.json with read-json.js, calls mans then mans_ and populates pkg.man with an array of roff files. Finally linkMans get's called and it iterates over pkg.man to symlink the file to the manDest as defined by:

var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
...
manDest = path.join(manRoot, "man" + sxn, bn)

So even npm's package.json file uses this method:

"directories": {
    "doc": "./doc",
    "man": "./man",
    "lib": "./lib",
    "bin": "./bin"
  },
  "main": "./lib/npm.js",
  "bin": {
    "npm": "./bin/npm-cli.js"
  },

mathiasbynens pushed a commit that referenced this pull request May 16, 2014
@mathiasbynens
Copy link
Owner

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants