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

Define custom help objects #2047

Open
bornova opened this issue Dec 1, 2020 · 14 comments
Open

Define custom help objects #2047

bornova opened this issue Dec 1, 2020 · 14 comments

Comments

@bornova
Copy link
Contributor

bornova commented Dec 1, 2020

For example, if I import a function as follows:

math.import({
  area: function (x, y) {
    return x*y
  }
})

I would like to then define a help object for this new function so I can call math.help("area"). Is this possible?

@josdejong
Copy link
Owner

josdejong commented Dec 3, 2020

That is a good question, there is is currently no solution for that.

Here is a workaround:

import { create, all, docs } from 'mathjs'

const math = create(all)

math.import({
  area: function (x, y) {
    return x*y
  }
})

const areaDocs = {
  name: 'area',
  category: 'Utils',
  syntax: [
    'area(x, y)'
  ],
  description: 'Calculate the area of a rectangle with width x and height y',
  examples: [
    'area(2, 3)',
    'area(2.5, 5)'
  ],
  seealso: ['multiply']
}

// Workaround: this will add docs to the static, global docs object
docs.area = areaDocs

console.log('area(2, 3) = ' + math.evaluate('area(2, 3)'))
// area(2, 3) = 6

console.log(math.format(math.evaluate('help(area)')))
// Name: area
//
// Category: Utils
//
// Description:
//     Calculate the area of a rectangle with width x and height y
//
// Syntax:
//     area(x, y)
//
// Examples:
//     area(2, 3)
//         6
//     area(2.5, 5)
//         12.5
//
// See also: multiply

Would be nice to create a function like createDocs for this. Anyone interested to pick this up?

@bornova
Copy link
Contributor Author

bornova commented Dec 3, 2020

Thanks @josdejong! A createDocs function would be a nice feature.

In the meantime, any way to make the workaround work in browser environment?

@josdejong
Copy link
Owner

In the meantime, any way to make the workaround work in browser environment?

hm, I'm afraid these docs are simply not exposed in the pre-baked browser bundle.

@rnd-debug
Copy link
Contributor

rnd-debug commented Dec 5, 2020

Hello. Voluntaring to pick up the createDocs not-for-browser implementation.
[Edit] Looks trickier than expected. First idea was to append to embeddedDocs, but it seems that this object will be shared between different instances of math created via

import math from 'src/defaultInstance.js'
const newMathInstance = math.create()

=> in particular, unit testing is not "unit" anymore. Did I missed something in creating different instances of math?

@josdejong
Copy link
Owner

Thanks @rnd-debug . Currently the embeddedDocs are a static, singleton object, not touched by anything, and shared by all mathjs instances. This indeed needs to become a copy per mathjs instance, where we then can create (and maybe also delete?) functions.

@gwhitney
Copy link
Collaborator

gwhitney commented Feb 7, 2022

I saw that the PR for this stalled for whatever reasons, but it still seems like a worthy goal. In particular, I just wanted to say that if something along these lines would allow the embedded docs for mathjs builtin functions to be specified in the same source file as the function itself, that would seem to be to be a real plus for mathjs internal code organization (or should that be a separate issue?). Even better is if the online and embedded docs could be fully unified -- the current system is not very "DRY". Presumably the unified doc object would have to have both "short" and "long" descriptions, because the embedded docs are generally much more terse, but if the other common stuff like name, syntax, examples, seealso, etc. could be specified just once that would be a win.

@josdejong
Copy link
Owner

Putting the embedded docs alongside the functions is an interesting idea @gwhitney ! What may be tricky though is that when a user does not need the embedded docs, it will not be bundled with his (frontend) application, since it adds a serious amount of bytes. I'm afraid that achieving both will result in a lot of extra plumbing and overhead in the library 🤔

@gwhitney
Copy link
Collaborator

Well, could it work for docgenerator.js to extract the JSON for the embedded docs (all those src/expression/embeddedDocs/**.js files) from the jsdoc-style comments in the main source files? Then the information wouldn't affect the size of a bundle that doesn't include the embedded docs, since the comments aren't included in the bundled js anyway. But then there would only be one source of information. This seems feasible given that docgenerator is running in the build process anyway. We'd likely need to add a "Summary" or "Brief Description" section to the comments, because the embedded help is often just a summary. But almost everything else can be reused as-is. If this sounds like it might be reasonable to you, I would be happy to try to make a PR for it, just let me know. (And since this has gotten somewhat far from the original question in this issue, if you want me to move this concept into a different issue just let me know.)

@josdejong
Copy link
Owner

Yes good point, getting off-topic here. I've opened a separate issue in #2454

@dvd101x
Copy link
Collaborator

dvd101x commented Jan 9, 2025

Hi, here is a workaround to add embedded docs for the browser.

math.import({
    area: function (x, y) {
        return x * y
    }
})

function createNewHelp() {
    const oldHelp = math.help
    const newDocs = {
        area: {
            name: 'area',
            category: 'Utils',
            syntax: [
                'area(x, y)'
            ],
            description: 'Calculate the area of a rectangle with width x and height y',
            examples: [
                'area(2, 3)',
                'area(2.5, 5)'
            ],
            seealso: ['multiply']
        }
    }

    return function help(x) {
        const nameString = typeof x === 'function' ? x.name : x
        if (nameString in newDocs) {
            return new math.Help(newDocs[nameString])
        } else {
            return oldHelp(x)
        }
    }
}

math.import({
    help: createNewHelp()
}, { override: true })

console.log('area(2, 3) = ' + math.evaluate('area(2, 3)'))
// area(2, 3) = 6

console.log(math.format(math.evaluate('help(area)')))
console.log(math.format(math.evaluate('help(sin)')))

Another possibility is to make use of the new importmap and then follow Jos's workaround.

@dvd101x
Copy link
Collaborator

dvd101x commented Jan 10, 2025

Jos's workaruond also works in the browser by importing from http://esm.run/mathjs (without importmap)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>

<body>

</body>
<script type="module">
    import { create, all, docs } from 'http://esm.run/mathjs'

    const math = create(all)

    math.import({
        area: function (x, y) {
            return x * y
        }
    })

    const areaDocs = {
        name: 'area',
        category: 'Utils',
        syntax: [
            'area(x, y)'
        ],
        description: 'Calculate the area of a rectangle with width x and height y',
        examples: [
            'area(2, 3)',
            'area(2.5, 5)'
        ],
        seealso: ['multiply']
    }

    // Workaround: this will add docs to the static, global docs object
    docs.area = areaDocs

    console.log('area(2, 3) = ' + math.evaluate('area(2, 3)'))
    // area(2, 3) = 6

    console.log(math.format(math.evaluate('help(area)')))
    // Name: area
    //
    // Category: Utils
    //
    // Description:
    //     Calculate the area of a rectangle with width x and height y
    //
    // Syntax:
    //     area(x, y)
    //
    // Examples:
    //     area(2, 3)
    //         6
    //     area(2.5, 5)
    //         12.5
    //
    // See also: multiply
</script>

</html>

@dvd101x
Copy link
Collaborator

dvd101x commented Jan 11, 2025

I found a way to include the embedded docs as a property of the imported function and it works ok if help searches for that property in the function. The issue is that when the imported thing is a primitive like a string or a number, then it can't include the doc property.

I could include a PR for importing docs for functions (and other stuff that can include properties). In the meantime a better solution is implemented.

Another possibility is for import to include in the options a property docs and save that somewhere like importedDocs for help to look for.

@dvd101x
Copy link
Collaborator

dvd101x commented Jan 11, 2025

Currently help uses as a signature any so another trick is to import a typed function with the signatures for string and function.

const newDocs = {
  'area': {
    name: 'area',
    category: 'Utils',
    syntax: [
      'area(x, y)'
    ],
    description: 'Calculate the area of a rectangle with width x and height y',
    examples: [
      'area(2, 3)',
      'area(2.5, 5)'
    ],
    seealso: ['multiply']
  }
}
math.import({ area: (x, y) => x * y })

function newHelp(str) {
  return str in newDocs ? new math.Help(newDocs[str]) : math.typed.find(math.help, 'any')(str)
}

math.import({
  help: math.typed('help', {
    'string': str => newHelp(str),
    'function': fn => newHelp(fn.name)
  })
})

console.log('area(2, 3) = ' + math.evaluate('area(2, 3)'))
// area(2, 3) = 6

console.log(math.format(math.evaluate('help(area)')))
console.log(math.format(math.evaluate('help("area")')))
console.log(math.format(math.evaluate('help(sin)')))

@josdejong
Copy link
Owner

Thanks for sharing all these solutions David!

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

No branches or pull requests

5 participants