• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sequana / sequana / 19136907997

06 Nov 2025 12:56PM UTC coverage: 68.835% (-0.4%) from 69.186%
19136907997

push

github

web-flow
Merge pull request #875 from cokelaer/dev

update HTML variant calling + others

157 of 332 new or added lines in 14 files covered. (47.29%)

5 existing lines in 3 files now uncovered.

14551 of 21139 relevant lines covered (68.83%)

2.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.94
/sequana/modules_report/base_module.py
1
# coding: utf-8
2
#
3
#  This file is part of Sequana software
4
#
5
#  Copyright (c) 2016 - Sequana Development Team
6
#
7
#  File author(s):
8
#      Dimitri Desvillechabrol <dimitri.desvillechabrol@pasteur.fr>,
9
#          <d.desvillechabrol@gmail.com>
10
#
11
#  Distributed under the terms of the 3-clause BSD license.
12
#  The full license is in the LICENSE file, distributed with this software.
13
#
14
#  website: https://github.com/sequana/sequana
15
#  documentation: http://sequana.readthedocs.io
16
#
17
##############################################################################
18
"""Generic module is the parent module of all other module"""
3✔
19
import base64
3✔
20
import io
3✔
21
import os
3✔
22
import shutil
3✔
23

24
import colorlog
3✔
25
from jinja2 import Environment, PackageLoader
3✔
26

27
# from reports import HTMLTable
28
from sequana.utils import config
3✔
29

30
logger = colorlog.getLogger(__name__)
3✔
31

32

33
__all__ = ["SequanaBaseModule"]
3✔
34

35

36
class SequanaBaseModule(object):
3✔
37
    """Generic Module to write HTML reports.
38

39

40
    # to add a TOC, add this code::
41

42
        <div id="tocDiv">
43
        <ul id="tocList"> </ul>
44
        </div>
45

46

47
    """
48

49
    def __init__(self, template_fn="standard.html", required_dir=None):
3✔
50
        if required_dir is None:
3✔
51
            self.required_dir = ("css", "js", "images")
3✔
52
        else:
53
            self.required_dir = required_dir
×
54

55
        self.output_dir = config.output_dir
3✔
56
        self.path = "./"
3✔
57
        # Initiate jinja template
58
        env = Environment(loader=PackageLoader("sequana", "resources/templates/"))
3✔
59
        self.template = env.get_template(template_fn)
3✔
60
        self._init_report()
3✔
61
        self._fotorama_js_added = False
3✔
62

63
    def _init_report(self):
3✔
64
        """Create the report directory. All necessary directories are copied
65
        in working directory.
66
        """
67
        # Be aware of #465 issue. We need to check that the target file is
68
        # valid, in which case there is no need to copy the files.
69

70
        # Create report directory
71
        if os.path.isdir(config.output_dir) is False:
3✔
72
            os.mkdir(self.output_dir)
×
73

74
        for directory in self.required_dir:
3✔
75
            complete_directory = os.sep.join([self.output_dir, directory])
3✔
76
            if os.path.isdir(complete_directory) is False:
3✔
77
                os.mkdir(complete_directory)
3✔
78

79
        # Copy css/js necessary files
80
        for filename in config.css_list:
3✔
81
            target = os.sep.join([self.output_dir, "css"])
3✔
82
            if os.path.isfile(target) is False:
3✔
83
                try:
3✔
84
                    shutil.copy(filename, target)
3✔
85
                except PermissionError:
×
86
                    pass
×
87

88
        for filename in config.js_list:
3✔
89
            target = os.sep.join([self.output_dir, "js"])
3✔
90
            if os.path.isfile(target) is False:
3✔
91
                try:
3✔
92
                    shutil.copy(filename, target)
3✔
93
                except PermissionError:
×
94
                    pass
×
95

96
        target = os.sep.join([self.output_dir, "images"])
3✔
97
        os.makedirs(target, exist_ok=True)
3✔
98
        if os.path.isfile(target) is False:
3✔
99
            try:
3✔
100
               shutil.copy(config.logo, target)
3✔
101
            except PermissionError:
×
102
                pass
×
103

104
    def create_html(self, output_filename):
3✔
105
        """Create HTML file with Jinja2.
106

107
        :param str output_filename: HTML output filename
108
        """
109
        if output_filename is None:
3✔
110
            return
3✔
111
        report_output = self.template.render(config=config, module=self)
3✔
112
        with open(os.sep.join([config.output_dir, output_filename]), "w") as fp:
3✔
113
            print(report_output, file=fp)
3✔
114

115
    def create_link(self, name, target, newtab=True, download=False):
3✔
116
        """Create an HTML hyperlink with name and target.
117

118
        :param str target: the target url.
119
        :param bool newtab: open html page in a new tab.
120
        :param bool download: download the target.
121

122
        Return as string the HTML hyperlink to the target.
123
        """
124
        link = '<a href="{0}" '
3✔
125
        if newtab:
3✔
126
            link += 'target="_blank" '
3✔
127
        if download:
3✔
128
            link += 'download="{0}" '
3✔
129
        link += ">{1}</a>"
3✔
130
        return link.format(target, name)
3✔
131

132
    def create_hide_section(self, html_id, name, content, hide=False):
3✔
133
        """Create an hideable section.
134

135
        :param str html_id: add short id to connect all elements.
136
        :param str name: name of the hyperlink to hide or display the content.
137
        :param str content: hideable HTML content.
138
        :param bool hide: set if the first state is hiding or not.
139

140
        Return tuple that contains HTML hyperlink and hideable section.
141
        """
142
        link = "<a href='#1' class='show_hide{0}'>{1}</a>".format(html_id, name)
3✔
143
        content = "<div class='slidingDiv{0}'>\n{1}\n</div>".format(html_id, content)
3✔
144
        hidden = ""
3✔
145
        if hide:
3✔
146
            hidden = '\n$(".slidingDiv{0}").hide();'.format(html_id)
3✔
147
        js = """
3✔
148
<script type="text/javascript">
149
    $(document).ready(function(){{{1}
150
        $(".show_hide{0}").click(function(){{
151
            $(".slidingDiv{0}").slideToggle();
152
        }});
153
    }});
154
</script>
155
        """.format(
156
            html_id, hidden
157
        )
158
        content = js + content
3✔
159
        return link, content
3✔
160

161
    def copy_file(self, filename, target_dir):
3✔
162
        """Copy a file to a target directory in report dir. Return the
163
        relative path of your file.
164

165
        :param str filename: file to copy.
166
        :param str target_dir: directory where to copy.
167

168
        Return relative path of the new file location.
169
        """
170
        directory = config.output_dir + os.sep + target_dir
3✔
171
        try:
3✔
172
            os.makedirs(directory)
3✔
173
        except FileExistsError:
3✔
174
            if os.path.isdir(directory):
3✔
175
                pass
3✔
176
            else:
177
                msg = "{0} exist and it is not a directory".format(directory)
×
178
                logger.error(msg)
×
179
                raise FileExistsError
×
180
        try:
3✔
181
            shutil.copy(filename, directory)
3✔
182
        except FileNotFoundError:
×
183
            msg = "{0} doesn't exist".format(filename)
×
184
            raise FileNotFoundError(msg)
×
185
        return target_dir + os.sep + os.path.basename(filename)
3✔
186

187
    def add_float_right(self, content):
3✔
188
        """Align a content to right."""
189
        return '<div style="float:right">{0}</div>'.format(content)
3✔
190

191
    def add_code_section(self, content, language):
3✔
192
        """Add code in your html."""
193
        html = '<div class="code"><pre><code class="{0}">{1}' "</code></pre></div>"
3✔
194
        return html.format(language, content)
3✔
195

196
    def include_svg_image(self, filename, alt="undefined"):
3✔
197
        """Include SVG image in the html."""
198
        html = '<object data="{0}" type="image/svg+xml">\n' '<img src="{0}" alt={1}></object>'
3✔
199
        return html.format(filename, alt)
3✔
200

201
    def png_to_embedded_png(self, png, style=None, alt="", title=""):
3✔
202
        """Include a PNG file as embedded file."""
203
        import base64
3✔
204

205
        with open(png, "rb") as fp:
3✔
206
            png = base64.b64encode(fp.read()).decode()
3✔
207
        if style:
3✔
208
            html = '<img style="{0}" alt="{1}" title="{2}"'.format(style, alt, title)
×
209
        else:
210
            html = '<img alt="{}" title="{}"'.format(alt, title)
3✔
211
        return '{0} src="data:image/png;base64,{1}">'.format(html, png)
3✔
212

213
    def create_embedded_png(self, plot_function, input_arg, style=None, **kwargs):
3✔
214
        """Take as a plot function as input and create a html embedded png
215
        image. You must set the arguments name for the output to connect
216
        buffer.
217
        """
218
        buf = io.BytesIO()
3✔
219
        # add buffer as output of the plot function
220
        kwargs = dict({input_arg: buf}, **kwargs)
3✔
221
        try:
3✔
222
            plot_function(**kwargs)
3✔
223
            html = "<img "
3✔
224
            if style:
3✔
225
                html += 'style="{0}"'.format(style)
3✔
226
            html += 'src="data:image/png;base64,{0}"/>'.format(base64.b64encode(buf.getvalue()).decode("utf-8"))
3✔
227
            buf.close()
3✔
UNCOV
228
        except Exception as err:
×
UNCOV
229
            print(err)
×
UNCOV
230
            html = "image not created"
×
231
        return html
3✔
232

233
    def create_combobox(self, path_list, html_id, newtab=True):
3✔
234
        """Create a dropdown menu with QueryJS.
235

236
        :param list path_list: list of links.
237

238
        return html div and js script as string.
239
        """
240
        option_list = (
3✔
241
            "<li>{0}</li>\n".format(self.create_link(os.path.basename(path), path, newtab)) for path in path_list
242
        )
243
        html = """
3✔
244
<div id="jq-dropdown-{1}" class="jq-dropdown jq-dropdown-tip jq-dropdown-scroll">
245
    <ul class="jq-dropdown-menu">
246
        {0}
247
    </ul>
248
</div>
249
<a href="#" data-jq-dropdown="#jq-dropdown-{1}">Subchromosome</a>
250
        """.format(
251
            "\n".join(option_list), html_id
252
        )
253
        return html
3✔
254

255
    def add_fotorama(
3✔
256
        self,
257
        files,
258
        width=600,
259
        height=800,
260
        loop=True,
261
        thumbnails=True,
262
        file_thumbnails=None,
263
        captions=None,
264
    ):
265
        if self._fotorama_js_added is False:
×
266
            script = """
×
267
        <!-- jQuery 1.8 or later, 33 KB -->
268
        <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>-->
269
        <!-- Fotorama from CDNJS, 19 KB -->
270
        <link  href="https://cdnjs.cloudflare.com/ajax/libs/fotorama/4.6.4/fotorama.css" rel="stylesheet">
271
        <script src="https://cdnjs.cloudflare.com/ajax/libs/fotorama/4.6.4/fotorama.js"></script>
272
        """
273
            self._fotorama_js_added = True
×
274
        else:
275
            script = ""
×
276

277
        if captions:
×
278
            if len(files) != len(captions):
×
279
                raise ValueError("captions and files must be of same length with 1-to-1 mapping")
×
280
        else:
281
            captions = [filename.name for filename in files]
×
282

283
        script += '<div class="fotorama" fzyz-keyboard="true" '
×
284
        if thumbnails is True:
×
285
            script += ' data-nav="thumbs"'
×
286
        if loop is True:
×
287
            script += ' data-loop="true"'
×
288
        script += ' data-width="{}"  data-height="{}"'.format(width, height)
×
289
        script += ">"
×
290
        for filename, caption in zip(files, captions):
×
291
            script += '<img src="{}" data-caption="{}">'.format(filename, caption)
×
292
        script += "</div>"
×
293
        return script
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc