pdoc3 / pdoc

:snake: :arrow_right: :scroll: Auto-generate API documentation for Python projects
https://pdoc3.github.io/pdoc/
GNU Affero General Public License v3.0
1.12k stars 145 forks source link

Incorrect syntax highlighting #32

Closed sobester closed 4 years ago

sobester commented 5 years ago

Expected Behavior

Syntax highlighting in code snipets in docstring

Actual Behavior

Syntax highlighted beautifully in some cases, partially in others (only some keywords, etc.), not at all in yet others.

Steps to Reproduce

Here is an example function where the resulting HTML has the code all in grey:

def tw2pw(thrusttoweight, speed, etap):
    """Converts thrust to weight to power to weight (propeller-driven aircraft)

    `PARAMETERS`
    ------------

    `thrusttoweight` : thrust to weight ratio (non-dimensional)

    `speed`: speed (in m/s if output in Watts / Newton is required)

    `etap`: propeller efficiency (non-dimensional)

    `RETURNS`
    -----------

    power to weight ratio (in W/N if speed is in m/s)

    `SEE ALSO`
    ------------
    ``powerrequired``

    `NOTES`
    ---------
    A note on units. If the input speed is in m/s, the other two inputs being
    non-dimensional, the output product is also in m/s, which is equal to W/N
    (W / N = (J/s) / N = (Nm/s) / N = m/s).

    `EXAMPLE`
    ---------

    ------------
        #!python
        import constraintanalysis as ca
        import atmospheres as at
        import unitconversions as co

        designbrief = {'stloadfactor': 2, 'turnalt_m': 3050, 'turnspeed_ktas': 140}

        etap = {'turn': 0.85}

        designperformance = {'CLmaxclean': 1.45, 'CDminclean': 0.02541,
                             'etaprop': etap}

        designdef = {'aspectratio': 10, 'sweep_le_deg': 2,
                     'sweep_mt_deg': 0, 'bpr': -1}

        TOW_kg = 1500

        designatm = at.Atmosphere()
        concept = ca.AircraftConcept(designbrief, designdef,
                                     designperformance, designatm)

        wingloading_pa = 1000

        twreq, _, _ = concept.twrequired_trn(wingloading_pa)

        turnspeed_mpstas = co.kts2mps(designbrief['turnspeed_ktas'])

        pw_trn_wpn = ca.tw2pw(twreq, turnspeed_mpstas, etap['turn'])
        pw_trn_hpkg = co.wn2hpkg(pw_trn_wpn)
        p_trn_hp = pw_trn_hpkg * TOW_kg

        print(p_trn_hp)

    ---
        #!python
        318.691213406
    """
    return thrusttoweight * speed / etap

Other, seemingly very similar docstrings in the same .py file are highlighted very nicely, for example:

    def twrequired_trn(self, wingloading_pa):
        """Calculates the T/W required for turning for a range of wing loadings

        `PARAMETERS`
        ------------

        `wingloading_pa` : float or numpy array, list of wing loading values in Pa.

        `RETURNS`
        -----------

        `twratio` : array, thrust to weight ratio required for the given wing loadings.

        `clrequired` : array, lift coefficient values required for the turn (see notes).

        `feasibletw`: as twratio, but contains NaNs in lieu of unachievable (CLmax exceeded) values.

        `SEE ALSO`
        ------------
        ``twrequired``

        `NOTES`
        ---------
        1. Use `twrequired` if a full constraint analysis is desired, as this integrates
        the take-off, turn, climb, cruise, and service ceiling constraints, as well as
        computing the combined constraint boundary.

        2. At the higher end of the wing loading range (low wing area values) the CL required
        to achieve the required turn rate may exceed the maximum clean CL (as specified in the
        `CLmaxclean` entry in the `performance` dictionary argument of the `AircraftConcept`
        class object being used). This means that, whatever the T/W ratio, the wings will stall
        at this point. The basic T/W value will still be returned in `twratio`, but there is
        another output, `feasibletw`, which is an array of the same T/W values, with those
        values blanked out (replaced with NaN) that cannot be achieved due to CL exceeding
        the maximum clean lift coefficient.

        `EXAMPLE`

        Given a load factor, an altitude (in a given atmosphere) and a true airspeed, as well as
        a set of basic geometrical and aerodynamic performance parameters, compute the necessary
        T/W ratio to hold that load factor in the turn.
        ------------
            #!python
            import atmospheres as at
            import constraintanalysis as ca
            import unitconversions as co

            designbrief = {'stloadfactor': 2, 'turnalt_m': co.feet2m(10000), 'turnspeed_ktas': 140}

            etap = {'turn': 0.85}

            designperformance = {'CLmaxclean': 1.45, 'CDminclean':0.02541, 'etaprop': etap}

            designdef = {'aspectratio': 10.12, 'sweep_le_deg': 2, 'sweep_mt_deg': 0, 'bpr': -1}

            designatm = at.Atmosphere()

            concept = ca.AircraftConcept(designbrief, designdef,
            designperformance, designatm)

            wingloadinglist_pa = [1250, 1500, 1750]

            twratio, clrequired, feasibletw = concept.twrequired_trn(wingloadinglist_pa)

            print('T/W:               ', twratio)
            print('Only feasible T/Ws:', feasibletw)
            print('CL required:       ', clrequired)
            print('CLmax clean:       ', designperformance['CLmaxclean'])

        ---
            #!python
            T/W:                [ 0.19920641  0.21420513  0.23243016]
            Only feasible T/Ws: [ 0.19920641  0.21420513         nan]
            CL required:        [ 1.06552292  1.2786275   1.49173209]
            CLmax clean:        1.45

        """

        if self.turnspeed_ktas == -1:
            turnmsg = "Turn speed not specified in the designbrief dictionary."
            raise ValueError(turnmsg)

        if self.stloadfactor == -1:
            turnmsg = "Turn load factor not specified in the designbrief dictionary."
            raise ValueError(turnmsg)

        wingloading_pa = actools.recastasnpfloatarray(wingloading_pa)

        # W/S at the start of the specified turn test may be less than MTOW/S
        wingloading_pa = wingloading_pa * self.turn_weight_fraction

        twratio, clrequired = self.thrusttoweight_sustainedturn(wingloading_pa)

        # What SL T/W will yield the required T/W at the actual altitude?
        temp_c = self.designatm.airtemp_c(self.turnalt_m)
        pressure_pa = self.designatm.airpress_pa(self.turnalt_m)
        density_kgpm3 = self.designatm.airdens_kgpm3(self.turnalt_m)
        turnspeed_mps = co.kts2mps(self.turnspeed_ktas)
        mach = self.designatm.mach(turnspeed_mps, self.turnalt_m)
        corr = self._altcorr(temp_c, pressure_pa, mach, density_kgpm3)

        twratio = twratio / corr

        # Map back to T/MTOW if turn start weight is less than MTOW
        twratio = twratio * self.turn_weight_fraction

        # Which of these points is actually reachable given the clean CLmax?
        feasibletw = np.copy(twratio)
        for idx, val in enumerate(clrequired):
            if val > self.clmaxclean:
                feasibletw[idx] = np.nan

        if len(twratio) == 1:
            return twratio[0], clrequired[0], feasibletw[0]

        return twratio, clrequired, feasibletw

I cannot figure what I'm doing right in one but not in the other...

Many thanks for the great tool.

Additional info

kernc commented 5 years ago

Can you attach the HTML output file? I'll wager it's the highlighting engine auto-detecting the wrong language.

sobester commented 5 years ago

here is the link to the html: https://www.dropbox.com/s/p1qoke0ko0xcy1l/constraintanalysis.html?dl=0

kernc commented 5 years ago

The issue appears to be that Highlight.js for some reason recognizes your code snippet as Makefile code. You can force the code language using fenced-code blocks with language specification instead of indented blocks, i.e.:

EXAMPLE
-------

```python
import constraintanalysis as ca
import atmospheres as at
import unitconversions as co
...

```

Other than that, this looks like an upstream issue. Filed as https://github.com/highlightjs/highlight.js/issues/1976.

sobester commented 5 years ago

The fence code blocks method seems to do the trick! Many thanks.

jetilton commented 5 years ago

I have a similar issue, that maybe goes under what am I doing wrong/is this right?

I see the above example @kernc uses markdown back ticks (``) in his docstring. Is this how you get the highlighting I see in this example, pymap3d?

I ask because I see that pdoc states that it has supported docstring formats (numpy, google), but I do not see any examples of what I should expect if I use these formats.

I have a project that uses numpy style docstrings and it just comes out looking like text. I also cloned the above pymap3d and ran pdoc and got the same result, which is unformatted text docstrings.

So what I expected:

<section class="desc"><p>converts target azimuth, elevation, range from observer at lat0,lon0,alt0 to ECEF coordinates.</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>az</code></strong> : <code>float</code> or <code>numpy.ndarray</code> of <code>float</code></dt>
<dd>azimuth to target</dd>
<dt><strong><code>el</code></strong> : <code>float</code> or <code>numpy.ndarray</code> of <code>float</code></dt>
<dd>elevation to target</dd>
<dt><strong><code>srange</code></strong> : <code>float</code> or <code>numpy.ndarray</code> of <code>float</code></dt>
<dd>slant range [meters]</dd>
<dt><strong><code>lat0</code></strong> : <code>float</code></dt>
<dd>Observer geodetic latitude</dd>
<dt><strong><code>lon0</code></strong> : <code>float</code></dt>
<dd>Observer geodetic longitude</dd>
<dt><strong><code>h0</code></strong> : <code>float</code></dt>
<dd>observer altitude above geodetic ellipsoid (meters)</dd>
<dt><strong><code>ell</code></strong> : <code>Ellipsoid</code>, optional</dt>
<dd>reference ellipsoid</dd>
<dt><strong><code>deg</code></strong> : <code>bool</code>, optional</dt>
<dd>degrees input/output
(False: radians in/out)</dd>
</dl>
<h2 id="returns">Returns</h2>
<p>ECEF (Earth centered, Earth fixed)
x,y,z</p>
<dl>
<dt><strong><code>x</code></strong> : <code>float</code> or <code>numpy.ndarray</code> of <code>float</code></dt>
<dd>ECEF x coordinate (meters)</dd>
<dt><strong><code>y</code></strong> : <code>float</code> or <code>numpy.ndarray</code> of <code>float</code></dt>
<dd>ECEF y coordinate (meters)</dd>
<dt><strong><code>z</code></strong> : <code>float</code> or <code>numpy.ndarray</code> of <code>float</code></dt>
<dd>ECEF z coordinate (meters)</dd>
</dl>
<h2 id="notes">Notes</h2>
<p>if srange==NaN, z=NaN</p></section>

What I got:

<div class="desc"><p>converts target azimuth, elevation, range from observer at lat0,lon0,alt0 to ECEF coordinates.</p>
<h2>Parameters</h2>
<p>az : float or numpy.ndarray of float
     azimuth to target
el : float or numpy.ndarray of float
     elevation to target
srange : float or numpy.ndarray of float
     slant range [meters]
lat0 : float
       Observer geodetic latitude
lon0 : float
       Observer geodetic longitude
h0 : float
     observer altitude above geodetic ellipsoid (meters)
ell : Ellipsoid, optional
      reference ellipsoid
deg : bool, optional
      degrees input/output  (False: radians in/out)</p>
<h2>Returns</h2>
<p>ECEF (Earth centered, Earth fixed)  x,y,z</p>
<p>x : float or numpy.ndarray of float
    ECEF x coordinate (meters)
y : float or numpy.ndarray of float
    ECEF y coordinate (meters)
z : float or numpy.ndarray of float
    ECEF z coordinate (meters)</p>
<h2>Notes</h2>
<p>if srange==NaN, z=NaN</p></div>

I just am not sure if I need to add markdown formatting manually or if I am not doing something correctly.

kernc commented 5 years ago

@jetilton Please open a new issue for this. The output should be as you expect it.

kernc commented 4 years ago

Upstream fixed https://github.com/highlightjs/highlight.js/issues/1976. Workaround (using fenced-code blocks) exists.