scilens 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. scilens/LICENSE +37 -0
  2. scilens/__init__.py +1 -0
  3. scilens/app.py +9 -0
  4. scilens/cli/__init__.py +0 -0
  5. scilens/cli/cglb.py +10 -0
  6. scilens/cli/config.py +15 -0
  7. scilens/cli/info.py +12 -0
  8. scilens/cli/main.py +74 -0
  9. scilens/components/analyse_folder.py +4 -0
  10. scilens/components/compare_2_files.py +22 -0
  11. scilens/components/compare_errors.py +22 -0
  12. scilens/components/compare_floats.py +39 -0
  13. scilens/components/compare_folders.py +39 -0
  14. scilens/components/executor.py +58 -0
  15. scilens/components/file_reader.py +29 -0
  16. scilens/config/__init__.py +0 -0
  17. scilens/config/cli_run_options.py +8 -0
  18. scilens/config/env_var.py +3 -0
  19. scilens/config/load.py +40 -0
  20. scilens/config/models/__init__.py +12 -0
  21. scilens/config/models/app.py +9 -0
  22. scilens/config/models/compare.py +5 -0
  23. scilens/config/models/compare_float_thresholds.py +4 -0
  24. scilens/config/models/execute.py +4 -0
  25. scilens/config/models/execute_and_compare.py +4 -0
  26. scilens/config/models/file_reader.py +3 -0
  27. scilens/config/models/reader_format_csv.py +18 -0
  28. scilens/config/models/reader_format_txt.py +5 -0
  29. scilens/config/models/readers.py +4 -0
  30. scilens/config/models/report.py +6 -0
  31. scilens/config/models/report_html.py +6 -0
  32. scilens/config/models/report_output.py +4 -0
  33. scilens/helpers/assets.py +27 -0
  34. scilens/helpers/search_and_index.py +26 -0
  35. scilens/helpers/templates/index.html +20 -0
  36. scilens/helpers/templates/style.css +124 -0
  37. scilens/processors/__init__.py +3 -0
  38. scilens/processors/analyse.py +14 -0
  39. scilens/processors/compare.py +7 -0
  40. scilens/processors/execute_and_compare.py +17 -0
  41. scilens/processors/models/__init__.py +0 -0
  42. scilens/processors/models/results.py +2 -0
  43. scilens/processors/processor_interface.py +9 -0
  44. scilens/readers/__init__.py +0 -0
  45. scilens/readers/exceptions.py +1 -0
  46. scilens/readers/reader_com_txt_lines.py +2 -0
  47. scilens/readers/reader_csv.py +73 -0
  48. scilens/readers/reader_interface.py +15 -0
  49. scilens/readers/reader_manager.py +30 -0
  50. scilens/readers/reader_txt.py +82 -0
  51. scilens/readers/transform.py +8 -0
  52. scilens/report/__init__.py +0 -0
  53. scilens/report/assets/logo.svg +9 -0
  54. scilens/report/assets/logo_cglb.svg +1 -0
  55. scilens/report/assets.py +11 -0
  56. scilens/report/html_report.py +26 -0
  57. scilens/report/report.py +30 -0
  58. scilens/report/template.py +8 -0
  59. scilens/report/templates/body_01_title.html +61 -0
  60. scilens/report/templates/body_99_footer.html +11 -0
  61. scilens/report/templates/compare_11_summary.html +75 -0
  62. scilens/report/templates/compare_12_sections.html +150 -0
  63. scilens/report/templates/compare_13_section_numbers copy.html +48 -0
  64. scilens/report/templates/compare_13_section_numbers.html +91 -0
  65. scilens/report/templates/index.html +92 -0
  66. scilens/report/templates/js_chartlibs_echarts.js +34 -0
  67. scilens/report/templates/js_chartlibs_plotly.js +55 -0
  68. scilens/report/templates/js_com_dom.js +21 -0
  69. scilens/report/templates/js_com_page.js +199 -0
  70. scilens/report/templates/js_com_palette.js +7 -0
  71. scilens/report/templates/js_curvemgr.js +319 -0
  72. scilens/report/templates/style.css +144 -0
  73. scilens/report/templates/utils_compare_report_anim.js +73 -0
  74. scilens/report/templates/utils_compare_report_framesseries.js +143 -0
  75. scilens/report/vendors/tailwindcss_3_4_15.js +83 -0
  76. scilens/report/vendors/tailwindcss_min_4.0.0-beta.4.js +26 -0
  77. scilens/run/__init__.py +0 -0
  78. scilens/run/models/task_results.py +5 -0
  79. scilens/run/models/task_runtime.py +2 -0
  80. scilens/run/run_task.py +41 -0
  81. scilens/run/standalone_task_runner.py +9 -0
  82. scilens/run/task_context.py +12 -0
  83. scilens/run/tasks_collector.py +52 -0
  84. scilens/utils/dict.py +6 -0
  85. scilens/utils/file.py +43 -0
  86. scilens/utils/php.py +15 -0
  87. scilens/utils/system.py +2 -0
  88. scilens/utils/template.py +5 -0
  89. scilens/utils/time_tracker.py +6 -0
  90. scilens/utils/vectors.py +4 -0
  91. scilens/utils/web.py +20 -0
  92. scilens-0.1.0.dist-info/METADATA +44 -0
  93. scilens-0.1.0.dist-info/RECORD +95 -0
  94. scilens-0.1.0.dist-info/WHEEL +4 -0
  95. scilens-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,26 @@
1
+ _A=None
2
+ import os
3
+ from pydantic import BaseModel
4
+ from scilens.utils.file import list_paths_for_file_recursive,text_write
5
+ from scilens.report.template import template_render_infolder
6
+ from scilens.report.assets import get_logo_image_src
7
+ class PathSearchInfo(BaseModel):path:str;dir:str;relative_path:str;relative_dir:str
8
+ CURRENT_DIR=os.path.dirname(os.path.realpath(__file__))
9
+ TEMPLATE_DIR=os.path.join(CURRENT_DIR,'templates')
10
+ class SearchAndIndex:
11
+ def file_discover(F,discover_path,filename):
12
+ D=discover_path;B=filename;E=[]
13
+ for C in list_paths_for_file_recursive(D,B):
14
+ A=C.replace(D,'')
15
+ if A.startswith(os.path.sep):A=A[1:]
16
+ E.append(PathSearchInfo(path=os.path.join(C,B),dir=C,relative_path=os.path.join(A,B),relative_dir=A))
17
+ return E
18
+ def file_list_info_from_origin(D,path_list,origin_path):
19
+ C=[]
20
+ for B in path_list:
21
+ A=B.replace(origin_path,'')
22
+ if A.startswith(os.path.sep):A=A[1:]
23
+ C.append(PathSearchInfo(path=B,dir=os.path.dirname(B),relative_path=A,relative_dir=os.path.dirname(A)))
24
+ return C
25
+ def search_and_create_html_index(A,search_path,search_filename,index_path,title=_A,logo=_A,logo_file=_A):B=A.file_discover(search_path,search_filename);A.create_html_index(B,index_path,title=title,logo=logo,logo_file=logo_file)
26
+ def create_html_index(B,results,index_path,title=_A,logo=_A,logo_file=_A):A=template_render_infolder('index.html',{'meta':{'title':title or'Index','image':logo or get_logo_image_src(logo_file)},'results':results},template_dir=TEMPLATE_DIR);text_write(index_path,A)
@@ -0,0 +1,20 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8"/>
4
+ <title id="head-title">{{ meta.title }}</title>
5
+ <style type="text/css">{% include 'style.css' %}</style>
6
+ </head>
7
+ <body>
8
+ <h1>
9
+ {% if meta.image %}<img height="40" src="{{ meta.image }}"/>{% endif %}
10
+ {{ meta.title }}
11
+ </h1>
12
+ <ul>
13
+ {% for item in results %}
14
+ <li>
15
+ <a target="_blank" href="{{ item.relative_path }}">{{ item.relative_dir }}</a>
16
+ </li>
17
+ {% endfor %}
18
+ </ul>
19
+ </body>
20
+ </html>
@@ -0,0 +1,124 @@
1
+ *:not(code):not(pre):not(td):not(th) {
2
+ font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
3
+ font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
4
+ }
5
+ h1, h2, h3 {
6
+ border-bottom: 1px solid #eee;
7
+ padding-bottom: 10px;
8
+ }
9
+ form { margin:0; padding:0;}
10
+ h4 {
11
+ margin-bottom: 0px;
12
+ }
13
+ table {
14
+ border-collapse: collapse;
15
+ }
16
+ table, th, td {
17
+ border: 1px solid #aaa;
18
+ font-size:0.8rem;
19
+ padding:1px 4px;
20
+ font-family: monospace;
21
+ white-space: nowrap;
22
+ }
23
+
24
+ fieldset {
25
+ border-radius: 5px;
26
+ color:#999999;
27
+ font-size:0.9rem;
28
+ border: 1px #999999 solid;
29
+ }
30
+
31
+ #section_parameters {
32
+ border-style: inset;
33
+
34
+ border-width: 1px;
35
+ padding:15px;
36
+
37
+ background-color: #fafafa;
38
+ }
39
+
40
+ th {
41
+ background-color:#eee;color:#333;
42
+ }
43
+ .number {
44
+ text-align: right;
45
+ }
46
+ .center {
47
+ text-align: center;
48
+ }
49
+ a, a:link, a:visited, a:hover, a:active { color: #666; }
50
+ .muted { font-size: 0.9rem; color:#999; }
51
+ .footer {text-align: center; padding:40px;}
52
+
53
+ .img_reader {
54
+ max-width: 600px;
55
+ max-height: 400px;
56
+ }
57
+
58
+ .tabs {
59
+ display: table;
60
+ width: 100%;
61
+ }
62
+ .tabs .tab, .tabs .tabend {
63
+ display: table-cell;
64
+ border: 1px solid transparent;
65
+ border-bottom: 1px solid #ccc;
66
+ }
67
+ .tabs .tab {
68
+ width: 1px;
69
+ padding:10px 15px;
70
+ border-radius: 8px 8px 0 0;
71
+ margin-left: 10px;
72
+ color: #777;
73
+ }
74
+ .tabs .tab:hover {
75
+ background-color: #eee;
76
+ cursor: pointer;
77
+ }
78
+ .tabs .tab.active {
79
+ border: 1px solid #ccc;
80
+ border-bottom: 1px solid transparent;
81
+ color: inherit;
82
+ }
83
+ /* tailwind like */
84
+ button {border: 0;user-select: none; /* Standard syntax */ cursor: pointer;}
85
+
86
+ .text-white { color: rgb(255 255 255); }
87
+
88
+ .text-xs { font-size: .75rem; line-height: 1rem; }
89
+
90
+ .py-1 { padding-bottom: .25rem; padding-top: .25rem; }
91
+ .py-2 { padding-bottom: .5rem; padding-top: .5rem; }
92
+ .py-3 { padding-bottom: .75rem; padding-top: .75rem; }
93
+
94
+ .px-1 { padding-left: .25rem; padding-right: .25rem; }
95
+ .px-2 { padding-left: .5rem; padding-right: .5rem; }
96
+ .px-3 { padding-left: .75rem; padding-right: .75rem; }
97
+
98
+ .mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; }
99
+ .my-1 { margin-top: 0.25rem; margin-bottom: 0.25rem; }
100
+
101
+ .m-1 { margin: .25rem; }
102
+ .m-2 { margin: .5rem; }
103
+ .m-3 { margin: .5rem; }
104
+ .m-4 { margin: 1rem; }
105
+
106
+ .rounded-lg { border-radius: .5rem; }
107
+
108
+ .bg-blue-700 { background-color: rgb(26 86 219); }
109
+ .bg-slate-400 { background-color: rgb(148 163 184); }
110
+ .bg-slate-500 { background-color: rgb(100 116 139); }
111
+ .bg-slate-600 { background-color: rgb(71 85 105); }
112
+
113
+ .hover\:bg-blue-800:hover { background-color: rgb(30 66 159); }
114
+
115
+
116
+ .shadow-sm { box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); }
117
+ .shadow { box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); }
118
+ .shadow-md { box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); }
119
+ .shadow-lg { box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); }
120
+ .shadow-xl { box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); }
121
+ .shadow-2xl { box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); }
122
+ .shadow-inner { box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); }
123
+ .shadow-none { box-shadow: 0 0 #0000; }
124
+
@@ -0,0 +1,3 @@
1
+ from.analyse import Analyse
2
+ from.compare import Compare
3
+ from.execute_and_compare import ExecuteAndCompare
@@ -0,0 +1,14 @@
1
+ import logging
2
+ from scilens.processors.models.results import ProcessorResults
3
+ from scilens.readers.reader_manager import ReaderManager
4
+ from scilens.run.task_context import TaskContext
5
+ from scilens.components.analyse_folder import AnalyseFolder
6
+ class Analyse:
7
+ def __init__(A,context):A.context=context
8
+ def process(A):
9
+ logging.info('Listing files to process');B=AnalyseFolder().get_list_dir(A.context.working_dir,exclude_files=[A.context.config_file]if A.context.config_file else None);logging.info(f"Number of files to process: {len(B)}");logging.info(f"Files to process: {B}")
10
+ for C in B:
11
+ logging.info(f"Processing file: {C}");D=ReaderManager().get_reader_from_file(C)
12
+ if not D:logging.warning(f"Reader not found for file: {C}");continue
13
+ logging.info(f"Reader: {D.__class__.__name__}")
14
+ return ProcessorResults()
@@ -0,0 +1,7 @@
1
+ import logging
2
+ from scilens.processors.models.results import ProcessorResults
3
+ from scilens.run.task_context import TaskContext
4
+ from scilens.components.compare_folders import CompareFolders
5
+ class Compare:
6
+ def __init__(A,context):B=context;A.context=B;A.compare_folders=CompareFolders(B)
7
+ def process(B):E='error';A=ProcessorResults();C=B.compare_folders.compute_list_filenames();logging.info(f"Number files to compare: {len(C)}");D=B.compare_folders.compute_comparison(C);A.warnings=[A[E]for A in D if A.get(E)];A.data=D;return A
@@ -0,0 +1,17 @@
1
+ import logging,os
2
+ from scilens.processors.models.results import ProcessorResults
3
+ from scilens.run.task_context import TaskContext
4
+ from scilens.config.models import ExecuteConfig
5
+ from scilens.components.executor import Executor
6
+ from scilens.components.compare_folders import CompareFolders
7
+ class ExecuteAndCompare:
8
+ def __init__(A,context):B=context;A.context=B;A.compare_folders=CompareFolders(B)
9
+ def process(D):
10
+ L='error';J='dir';I='label';B='config';H=ProcessorResults();E=D.context.config.execute;F=D.context.config.execute_and_compare;M=os.path.join(D.context.working_dir,F.test.working_dir)if F.test.working_dir else D.compare_folders.test_base;N=os.path.join(D.context.working_dir,F.reference.working_dir)if F.reference.working_dir else D.compare_folders.ref_base;G=[{I:'test',J:M,B:F.test}]
11
+ if not F.test_only:G.append({I:'reference',J:N,B:F.reference})
12
+ for A in G:
13
+ if not A[B]:C=E
14
+ else:C=ExecuteConfig();C.pre_folder_delete=A[B].pre_folder_delete or E.pre_folder_delete;C.pre_folder_creation=A[B].pre_folder_creation or E.pre_folder_creation;C.exe_path=A[B].exe_path or E.exe_path;C.exe_url=A[B].exe_url or E.exe_url;C.exe_url_headers=A[B].exe_url_headers or E.exe_url_headers;C.exe_unzip_and_use=A[B].exe_unzip_and_use or E.exe_unzip_and_use;C.exe_guess_os_extension=A[B].exe_guess_os_extension or E.exe_guess_os_extension;C.command_suffix=A[B].command_suffix or E.command_suffix
15
+ logging.info(f"Execute {A[I]} Command")
16
+ with Executor(A[J],C,alternative_working_dir=D.context.origin_working_dir)as O:O.process()
17
+ G=D.compare_folders.compute_list_filenames();logging.info(f"Number files to compare: {len(G)}");K=D.compare_folders.compute_comparison(G);H.warnings=[A[L]for A in K if A.get(L)];H.data=K;return H
File without changes
@@ -0,0 +1,2 @@
1
+ from pydantic import BaseModel
2
+ class ProcessorResults(BaseModel):data:list=[];errors:list[str]=[];warnings:list[str]=[]
@@ -0,0 +1,9 @@
1
+ _A='Plugins must implement the method'
2
+ from abc import ABC,abstractmethod
3
+ class ProcessorInterface(ABC):
4
+ @property
5
+ @abstractmethod
6
+ def _components(self):0
7
+ def __init__(A,path,name='',encoding='',ignore=None,curve_parser=None,reader_options=None):raise NotImplementedError(_A)
8
+ def class_info(A):raise NotImplementedError(_A)
9
+ def info(A):B={'reader':A.__class__.__name__,'reader_category':A.category,'name':A.name};B.update(A.class_info());return B
File without changes
@@ -0,0 +1 @@
1
+ class NoReaderFound(Exception):0
@@ -0,0 +1,2 @@
1
+ class ReaderComTxtLines:
2
+ def __init__(A,raw_lines):A.raw_lines=raw_lines;A.ignore_lines=[]
@@ -0,0 +1,73 @@
1
+ _E='charts'
2
+ _D='x_index'
3
+ _C='csv_col_index'
4
+ _B='curves'
5
+ _A=None
6
+ import csv
7
+ from scilens.readers.reader_interface import ReaderInterface
8
+ from scilens.config.models import ReaderCsvConfig
9
+ from scilens.config.models.reader_format_csv import ReaderCsvCurveParserNameConfig
10
+ from scilens.components.compare_floats import CompareFloats
11
+ def is_num(x):
12
+ try:return float(x)
13
+ except ValueError:return
14
+ def csv_row_detect_header(first_row):
15
+ A=first_row
16
+ if all(not A.isdigit()for A in A):return True,A
17
+ else:return False,[f"Column {A}"for(A,B)in enumerate(A)]
18
+ def csv_row_detect_cols_num(row):return[A for(A,B)in enumerate(row)if is_num(B)!=_A]
19
+ def csv_detect(path):
20
+ with open(path,'r')as B:A=csv.reader(B);C=next(A);D,E=csv_row_detect_header(C);F=next(A);G=csv_row_detect_cols_num(F);return D,E,G
21
+ class ReaderCsv(ReaderInterface):
22
+ category='datalines';extensions=['CSV']
23
+ def read(A,reader_options):
24
+ B=reader_options;A.reader_options=B;C,G,H=csv_detect(A.origin.path);A.has_header=C;A.cols=G;A.numeric_col_indexes=H
25
+ if B.ignore_colunmns:
26
+ if not C:raise Exception('Ignore columns is not supported without header.')
27
+ A.numeric_col_indexes=[C for C in A.numeric_col_indexes if A.cols[C]not in B.ignore_colunmns]
28
+ E=open(A.origin.path,'r',encoding=A.encoding);I=csv.reader(E);D=[]
29
+ for J in I:D+=[J]
30
+ if C:D.pop(0)
31
+ E.close();A.data_rows=D;A.raw_lines_number=len(A.data_rows)+(1 if C else 0);A.curves=_A
32
+ if B.curve_parser:
33
+ if B.curve_parser.name==ReaderCsvCurveParserNameConfig.COL_X:
34
+ A.curves,K=A._get_curves_col_x(B.curve_parser.parameters.x)
35
+ if A.curves:A.curves_parser_type=B.curve_parser.name;A.curves_info=K
36
+ elif B.curve_parser.name==ReaderCsvCurveParserNameConfig.COLS_COUPLE:raise NotImplementedError('cols_couple not implemented')
37
+ else:raise Exception('Curve parser not supported.')
38
+ A.cols_data=[_A]*len(A.cols)
39
+ for F in A.numeric_col_indexes:A.cols_data[F]=[float(A[F])for A in D]
40
+ def compare(A,compare_floats,param_reader,param_is_ref=True):
41
+ S='error';R='Errors limit reached';J=param_is_ref;I=param_reader;H=compare_floats;B=A if J else I;E=A if not J else I
42
+ if len(B.numeric_col_indexes)!=len(E.numeric_col_indexes):T=f"Number Float columns indexes are different: {len(B.numeric_col_indexes)} != {len(E.numeric_col_indexes)}";return T,_A
43
+ K=0;D=[''for A in B.cols];L=_A;F=_A
44
+ if A.curves and A.curves_parser_type==ReaderCsvCurveParserNameConfig.COL_X:M=A.curves_info[_D];L=B.cols_data[M];F=A.cols[M]
45
+ N=False
46
+ for C in range(len(B.cols_data)):
47
+ if B.cols_data[C]==_A:continue
48
+ if N:D[C]=R;continue
49
+ U=B.cols_data[C];V=E.cols_data[C];W=H.compare_errors.add_group(A.cols[C],data={'info_prefix':F}if F else _A);X,Y,O=H.compare_vectors(U,V,group_idx=W,info_vector=L);K+=Y
50
+ if X:N=True;D[C]=R;continue
51
+ if O[S]>0:D[C]=f"{O[S]} comparison errors"
52
+ if A.curves:
53
+ for P in A.curves[_E]:
54
+ Q=0
55
+ for Z in P[_B]:
56
+ G=A.curves[_B][Z]
57
+ if D[G[_C]]:G['comparison_error']=D[G[_C]];Q+=1
58
+ P['comparison']={'curves_nb_with_error':Q}
59
+ return _A,{'type':'vectors','total_diffs':K,'cols_has_error':D}
60
+ def class_info(A):return{'cols':A.cols,'raw_lines_number':A.raw_lines_number,_B:A.curves}
61
+ def _get_curves_col_x(E,col_x):
62
+ J='title';A=col_x;F={};B=E.cols
63
+ if isinstance(A,int):
64
+ C=A-1
65
+ if C<0 or C>=len(B):raise Exception('curve parser col_x: col_index is out of range.')
66
+ if isinstance(A,str):A=[A]
67
+ if isinstance(A,list):
68
+ H=[B for(B,C)in enumerate(B)if C in A]
69
+ if len(H)==0:return _A,F
70
+ C=H[0]
71
+ F[_D]=C;K=[B for(A,B)in enumerate(E.numeric_col_indexes)if A!=C];G=[];I=[]
72
+ for D in K:L={J:B[D],'short_title':B[D],'series':[[float(A[C]),float(A[D])]for A in E.data_rows],_C:D};G+=[L];M={J:B[D],'type':'simple','xaxis':B[C],'yaxis':B[D],_B:[len(G)-1]};I+=[M]
73
+ return{_B:G,_E:I},F
@@ -0,0 +1,15 @@
1
+ _A='Plugins must implement the method'
2
+ from abc import ABC,abstractmethod
3
+ from pydantic import BaseModel
4
+ class ReaderOrigin(BaseModel):type:str;path:str;short_name:str=''
5
+ class ReaderInterface(ABC):
6
+ @property
7
+ @abstractmethod
8
+ def category(self):0
9
+ def __init__(A,origin,name='',encoding='',curve_parser=None):A.origin=origin;A.name=name;A.encoding=encoding or'utf-8';A.curve_parser=curve_parser;A.read_error=None;A.read_data={}
10
+ def read(A,reader_options=None):raise NotImplementedError(_A)
11
+ def compare(A,reader,param_is_ref=True):raise NotImplementedError(_A)
12
+ def get_raw_lines(A,line_nb,pre=0,post=0):raise NotImplementedError(_A)
13
+ def class_info(A):raise NotImplementedError(_A)
14
+ def close(A):0
15
+ def info(A):B={'reader':A.__class__.__name__,'reader_category':A.category,'name':A.name,'origin':A.origin.dict(),'encoding':A.encoding,'read_error':A.read_error,'read_data':A.read_data};B.update(A.class_info());return B
@@ -0,0 +1,30 @@
1
+ import os,sys
2
+ from importlib.metadata import entry_points
3
+ from scilens.readers.exceptions import NoReaderFound
4
+ from scilens.readers.reader_interface import ReaderOrigin
5
+ def extension_format(extension):
6
+ A=extension
7
+ if A.startswith('.'):A=A[1:]
8
+ return A.upper()
9
+ from scilens.readers.reader_txt import ReaderTxt
10
+ from scilens.readers.reader_csv import ReaderCsv
11
+ BUILTIN_PLUGINS=[ReaderTxt,ReaderCsv]
12
+ LIB_PLUGINS_ENTRY_POINT='scilens.reader_plugins'
13
+ class ReaderManager:
14
+ def __init__(A):
15
+ A.plugins=[]+BUILTIN_PLUGINS;B=entry_points(group=LIB_PLUGINS_ENTRY_POINT)if sys.version_info.minor>=12 else entry_points().get(LIB_PLUGINS_ENTRY_POINT,[])
16
+ for C in B:A.plugins+=C.load()()
17
+ def _get_plugin_names(A):return[A.__name__ for A in A.plugins]
18
+ def __str__(A):return f"plugins: {A._get_plugin_names()}"
19
+ def _get_reader_from_extension(B,extension):
20
+ for A in B.plugins:
21
+ if extension_format(extension)in A.extensions:return A
22
+ def get_reader_from_file(D,path,name='',encoding='',curve_parser=None,extension_mapping=None,extension_fallback=None):
23
+ F=extension_fallback;E=extension_mapping;C=path;J,A=os.path.splitext(C);A=extension_format(A)
24
+ if E:
25
+ for(G,H)in E.items():
26
+ if extension_format(G)==A:A=extension_format(H);break
27
+ B=D._get_reader_from_extension(A)
28
+ if not B and F:B=D._get_reader_from_extension(F)
29
+ if not B:raise NoReaderFound(f"Reader cound not be derived")
30
+ I=ReaderOrigin(type='file',path=C,short_name=os.path.basename(C));return B(I,name=name,encoding=encoding,curve_parser=curve_parser)
@@ -0,0 +1,82 @@
1
+ _F='floats'
2
+ _E='lines'
3
+ _D=True
4
+ _C=False
5
+ _B='line'
6
+ _A=None
7
+ from scilens.readers.reader_interface import ReaderInterface
8
+ from scilens.readers.transform import string_2_floats
9
+ from scilens.config.models import ReaderTxtConfig
10
+ from scilens.components.compare_errors import SEVERITY_ERROR
11
+ from scilens.components.compare_floats import CompareFloats
12
+ class ReaderTxt(ReaderInterface):
13
+ category='datalines';extensions=['TXT']
14
+ def read(A,config):
15
+ F='_';B=config;A.reader_options=B;A.get_lines_pre=1;A.get_lines_post=1
16
+ if B.report_lines:
17
+ C=_C
18
+ for(I,G)in B.report_lines.items():
19
+ if I==A.origin.short_name:C=_D;A.get_lines_pre=G.pre;A.get_lines_post=G.post
20
+ if not C and B.report_lines.get(F):A.get_lines_pre=B.report_lines[F].pre;A.get_lines_post=B.report_lines[F].post
21
+ D=_A
22
+ if B.ignore:
23
+ C=_C
24
+ for(I,G)in B.ignore.items():
25
+ if I==A.origin.short_name:C=_D;D=G
26
+ if not C and B.ignore.get(F):D=B.ignore[F]
27
+ K=open(A.origin.path,'r',encoding=A.encoding);A.raw_lines=K.readlines();K.close();A.raw_lines_number=len(A.raw_lines);E=[]
28
+ if D:
29
+ C,O=A.find_patterns_lines_nb([A.pattern for A in D])
30
+ for(P,L)in enumerate(D):
31
+ H=O[P][_E]
32
+ if H:H=A.get_raw_siblings_nb(H,pre=L.pre,post=L.post);E+=H
33
+ E=list(set(E));E.sort()
34
+ A.ignore_patterns=[A.pattern for A in D]if D else[];A.ignore_lines=E;A.ignore_lines_number=len(E);A.curves=A.curve_parser(A.raw_lines)if A.curve_parser else _A;J=[]
35
+ for(Q,R)in enumerate(A.raw_lines):
36
+ M=string_2_floats(R)
37
+ if M:
38
+ N=Q+1
39
+ if not N in E:J.append({_B:N,_F:M})
40
+ A.floats_lines=J;A.floats_lines_number=len(J)
41
+ if B.error_rule_patterns:
42
+ C,S=A.find_patterns_lines_nb(B.error_rule_patterns);A.read_data['error_rule_patterns']={'found':C,'data':S}
43
+ if C:A.read_error='String error pattern found'
44
+ def compare(I,compare_floats,param_reader,param_is_ref=_D):
45
+ K=param_is_ref;J=param_reader;D=compare_floats;A=I if K else J;B=I if not K else J;E=0;C=_C;Y=[]
46
+ if A.floats_lines_number!=B.floats_lines_number:T=f"Nb number lines 1: {A.floats_lines_number} 2: {B.floats_lines_number} different";return T,_A
47
+ L=A.floats_lines;U=B.floats_lines
48
+ for M in range(len(L)):
49
+ F=L[M];G=U[M];H=F[_F];N=G[_F];O={'line_nb_1':F[_B],'line_nb_2':G[_B],'line_1':A.get_raw_lines(F[_B]),'line_2':B.get_raw_lines(G[_B])}
50
+ if len(H)!=len(N):
51
+ if not C:E+=1;C=D.compare_errors.add(SEVERITY_ERROR,'Not same numbers number in the lines',_A,info=O)
52
+ continue
53
+ for P in range(len(H)):
54
+ Q=H[P];R=N[P];V=Q-R
55
+ if V==0:continue
56
+ else:
57
+ E+=1
58
+ if not C:
59
+ W,X,S=D.compare_2_values(Q,R)
60
+ if S:C=D.compare_errors.add(W,X,S,info=O)
61
+ return _A,{'type':_E,'total_diffs':E}
62
+ def find_patterns_lines_nb(D,patterns):
63
+ A=patterns;B=_C;map={A:[]for A in A}
64
+ for(E,F)in enumerate(D.raw_lines):
65
+ for C in A:
66
+ if F.find(C)!=-1:map[C].append(E+1);B=_D
67
+ return B,[{'pattern':A,_E:map[A]}for A in A]
68
+ def get_raw_siblings_nb(A,lines_nb_array,pre=_A,post=_A):
69
+ B=[]
70
+ for C in lines_nb_array:
71
+ min=C-(pre if pre is not _A else A.get_lines_pre);max=C+(post if post is not _A else A.get_lines_post)+1
72
+ if min<0:min=0
73
+ if max>A.raw_lines_number+1:max=A.raw_lines_number+1
74
+ for D in range(min,max):
75
+ if D not in B:B.append(D)
76
+ return B
77
+ def get_raw_lines(A,line_nb,pre=_A,post=_A):
78
+ B=line_nb;min=B-1-(pre if pre is not _A else A.get_lines_pre);max=B-1+(post if post is not _A else A.get_lines_post)+1
79
+ if min<0:min=0
80
+ if max>A.raw_lines_number:max=A.raw_lines_number
81
+ return''.join([A.raw_lines[B]for B in range(min,max)])
82
+ def class_info(A):return{'raw_lines_number':A.raw_lines_number,'ignore_patterns':A.ignore_patterns,'ignore_lines_number':A.ignore_lines_number,'ignore_lines':A.ignore_lines,'floats_lines_number':A.floats_lines_number,'curves':A.curves}
@@ -0,0 +1,8 @@
1
+ def string_2_floats(text):
2
+ B=[];C=text.split()
3
+ for D in range(len(C)):
4
+ try:
5
+ A=float(C[D])
6
+ if A!=float('inf')and A!=float('-inf'):B.append(A)
7
+ except ValueError:pass
8
+ return B
File without changes
@@ -0,0 +1,9 @@
1
+ <svg version="1.2" viewBox="0 0 10000 10000" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="gradient1" x1="1670" y1="-733" x2="8290" y2="10733" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" style="stop-color:rgb(142,36,170)"/>
5
+ <stop offset="1" style="stop-color:rgb(255,64,129)"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <path style="fill:url(#gradient1)" d="M 6080,5251 L 6068,5224 6053,5196 6010,5141 5954,5085 5886,5029 5805,4973 5715,4919 5615,4867 5507,4817 5270,4729 5013,4659 4880,4633 4745,4614 4610,4602 4475,4597 400,4629 424,4064 495,3532 609,3033 764,2568 855,2349 956,2139 1065,1938 1182,1747 1308,1564 1441,1391 1581,1228 1728,1074 1892,1352 1931,1418 1960,1474 1971,1496 1978,1515 1981,1523 1982,1529 1982,1534 1981,1536 1981,1538 1940,1614 1904,1693 1874,1774 1849,1857 1830,1942 1816,2027 1808,2112 1805,2198 1808,2285 1816,2370 1830,2455 1849,2538 1874,2620 1904,2701 1940,2779 1981,2855 2027,2929 2077,2999 2132,3066 2191,3129 2255,3188 2322,3243 2392,3294 2465,3340 2542,3381 2621,3416 2702,3446 2785,3471 2869,3490 2955,3504 3040,3513 3126,3515 3212,3513 3298,3504 3383,3490 3466,3471 3548,3446 3628,3416 3707,3381 3783,3340 3856,3294 3927,3243 3994,3188 4057,3129 4116,3066 4171,2999 4222,2929 4268,2855 4308,2779 4344,2701 4374,2620 4399,2538 4418,2455 4432,2370 4440,2285 4443,2198 4440,2112 4432,2027 4418,1942 4399,1857 4374,1774 4344,1693 4308,1614 4268,1538 4222,1464 4171,1394 4116,1327 4057,1264 3994,1205 3927,1149 3856,1099 3783,1053 3707,1012 3628,977 3548,946 3466,922 3383,902 3298,888 3212,880 3126,877 3040,880 2955,888 2869,902 2785,922 2702,946 2621,977 2542,1012 2465,1053 2464,1053 2463,1054 2461,1053 2459,1053 2454,1051 2448,1048 2434,1039 2416,1026 2374,990 2324,944 2213,842 2111,751 2104,744 2396,543 2703,373 3022,233 3351,126 3689,51 4035,9 4385,1 4738,27 4724,1018 5295,1041 5832,1119 6334,1247 6801,1420 7234,1633 7631,1882 7992,2162 8316,2467 8604,2794 8855,3136 9069,3490 9245,3850 9382,4212 9481,4570 9541,4920 9561,5258 9561,5261 9549,5812 9534,6022 9514,6199 9489,6352 9460,6490 9393,6754 9282,7057 9158,7346 9021,7621 8872,7883 8708,8131 8532,8366 8343,8586 8140,8793 7925,8987 7696,9166 7454,9333 7198,9485 6930,9624 6648,9749 6353,9860 6045,9958 5834,9985 5629,9998 5431,9999 5238,9986 5052,9960 4871,9921 4697,9868 4529,9802 4367,9723 4211,9631 4060,9525 3916,9407 3778,9274 3646,9129 3520,8970 3400,8799 3336,8572 3304,8352 3299,8140 3321,7937 3365,7744 3430,7562 3514,7392 3612,7235 3724,7092 3846,6964 3977,6852 4112,6757 4251,6681 4389,6623 4526,6585 4657,6568 4762,6581 4862,6602 4954,6628 5041,6661 5122,6700 5196,6744 5263,6794 5324,6849 5378,6908 5426,6972 5466,7040 5500,7112 5527,7187 5546,7266 5559,7348 5564,7432 5558,7505 5544,7575 5522,7642 5493,7706 5458,7765 5417,7820 5371,7871 5320,7916 5266,7956 5209,7990 5150,8019 5089,8041 5026,8056 4964,8064 4901,8065 4840,8057 4884,7990 4904,7955 4923,7920 4938,7884 4950,7849 4957,7813 4960,7777 4959,7759 4956,7741 4952,7724 4946,7706 4939,7688 4929,7671 4918,7654 4904,7637 4871,7604 4828,7571 4774,7540 4710,7509 4659,7506 4610,7507 4563,7511 4518,7519 4476,7531 4435,7547 4397,7565 4361,7587 4327,7612 4296,7639 4268,7669 4242,7702 4197,7773 4163,7853 4141,7939 4130,8030 4131,8125 4145,8223 4171,8322 4210,8420 4264,8518 4331,8612 4407,8666 4493,8705 4589,8730 4692,8741 4803,8739 4919,8725 5040,8698 5164,8659 5417,8547 5669,8392 5912,8198 6134,7968 6328,7706 6483,7415 6543,7260 6590,7098 6623,6931 6640,6759 6641,6583 6624,6402 6587,6217 6531,6029 6453,5838 6353,5644 6229,5448 6080,5251 Z M 3797,2174 L 3795,2219 3791,2264 3783,2309 3773,2352 3760,2395 3743,2437 3724,2478 3702,2518 3677,2556 3651,2593 3622,2627 3591,2660 3559,2691 3524,2719 3488,2746 3449,2771 3409,2793 3368,2812 3325,2827 3282,2840 3238,2850 3193,2857 3147,2861 3101,2862 3055,2861 3010,2857 2965,2850 2921,2840 2877,2827 2835,2812 2794,2793 2754,2771 2715,2746 2678,2719 2644,2691 2611,2660 2581,2627 2552,2593 2525,2556 2501,2518 2478,2478 2459,2437 2443,2395 2429,2352 2419,2309 2412,2264 2407,2219 2406,2174 2407,2129 2412,2084 2419,2039 2429,1994 2443,1951 2459,1908 2478,1866 2501,1826 2525,1787 2552,1751 2581,1716 2611,1684 2644,1653 2678,1624 2715,1598 2754,1573 2794,1551 2835,1532 2877,1516 2921,1503 2965,1494 3010,1487 3055,1483 3101,1482 3147,1483 3193,1487 3238,1494 3282,1503 3325,1516 3368,1532 3409,1551 3449,1573 3488,1598 3524,1624 3559,1653 3591,1684 3622,1716 3651,1751 3677,1787 3702,1826 3724,1866 3743,1908 3760,1951 3773,1994 3783,2039 3791,2084 3795,2129 3797,2174 Z"/>
9
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10000 10000"><g fill="#183E93">_<path d="M2837 4554c-395 0-746-83-1055-250-308-167-549-397-723-688-174-292-261-623-261-995 0-376 87-709 261-998 174-290 415-516 723-681 309-165 660-247 1055-247 367 0 691 74 971 222 279 148 492 366 638 653l-677 395c-113-179-251-310-413-395s-338-127-526-127c-216 0-411 47-585 141s-311 229-410 406c-98 176-148 386-148 631s50 455 148 631c99 177 236 312 410 406s369 141 585 141c188 0 364-42 526-127s300-216 413-395l677 395c-146 282-359 500-638 653-280 153-604 229-971 229z"/><path d="M6859 5923c-348 0-688-46-1019-138-332-91-604-224-815-398l395-663c164 136 373 245 624 328 252 82 507 123 766 123 414 0 717-95 910-286 193-190 289-476 289-857v-199c-116 128-250 231-402 309-254 129-541 194-861 194-357 0-681-77-970-229-289-153-517-366-684-639s-251-593-251-960c0-362 84-679 251-952s395-485 684-635c289-151 613-226 970-226 320 0 607 64 861 191 171 85 319 203 445 352V737h839v3196c0 682-174 1185-522 1507s-851 483-1510 483zm21-2335c221 0 419-46 593-138 174-91 309-218 406-381 96-162 144-349 144-561 0-211-48-398-144-561-97-162-232-287-406-374s-372-130-593-130-420 43-596 130-313 212-409 374c-97 163-145 350-145 561 0 212 48 399 145 561 96 163 233 290 409 381 176 92 375 138 596 138z"/></g><g fill="#EE614E" ><path d="M515 9572V4860h991v4712H515z"/><path d="M7889 9623c-301 0-559-63-775-190-114-67-213-152-299-256v395h-946V4860h991v1658c80-88 171-163 273-222 216-127 468-191 756-191 321 0 610 72 866 216s460 347 610 610c150 262 225 573 225 933 0 356-75 665-225 927-150 263-354 467-610 613s-545 219-866 219zm-172-813c165 0 312-38 441-114s233-185 312-327c78-142 117-310 117-505 0-199-39-368-117-508-79-140-183-247-312-324-129-76-276-114-441-114s-313 38-444 114c-132 77-235 184-312 324-76 140-114 309-114 508 0 195 38 363 114 505 77 142 180 251 312 327 131 76 279 114 444 114z"/><path d="M4384 9572v-424c-75 125-176 226-305 304-191 114-436 171-737 171-266 0-495-46-685-136-191-91-337-215-439-372-101-156-152-332-152-527 0-203 50-381 149-533 100-153 257-272 473-359s498-130 845-130h787c0-212-64-377-193-496-130-118-327-178-594-178-182 0-361 29-537 86-175 57-325 135-447 232l-356-692c186-131 411-233 673-305 263-72 529-108 800-108 521 0 925 123 1213 368 288 246 432 629 432 1150v1949h-927zm-64-1428h-679c-233 0-393 38-480 114s-130 171-130 286c0 127 49 227 149 301s236 111 410 111c165 0 313-39 444-117 132-78 227-193 286-346v-349z"/></g></svg>
@@ -0,0 +1,11 @@
1
+ import os,base64
2
+ from mimetypes import MimeTypes
3
+ CURRENT_DIR=os.path.dirname(os.path.realpath(__file__))
4
+ ASSETS_DIR=os.path.join(CURRENT_DIR,'assets')
5
+ def get_image_base64(path):
6
+ with open(path,'rb')as A:return base64.b64encode(A.read()).decode('utf-8')
7
+ def get_image_base64_local(path):return get_image_base64(os.path.join(ASSETS_DIR,path))
8
+ def get_logo_image_src(logo_file):
9
+ A=logo_file
10
+ if not A:A=os.path.join(ASSETS_DIR,'logo.svg')
11
+ B=MimeTypes().guess_type(A)[0]or'image/*';C=get_image_base64(A);return f"data:{B};base64,"+C
@@ -0,0 +1,26 @@
1
+ import logging,os
2
+ from mimetypes import MimeTypes
3
+ from scilens.app import pkg_name,pkg_version,pkg_homepage,product_name,powered_by
4
+ from scilens.config.models import ReportConfig
5
+ from scilens.report.template import template_render_infolder
6
+ from scilens.report.assets import get_image_base64,get_image_base64_local,get_logo_image_src
7
+ from scilens.utils.time_tracker import TimeTracker
8
+ class HtmlReport:
9
+ def __init__(A,config,alt_config_dirs,working_dir=None):A.config=config;A.alt_config_dirs=alt_config_dirs;A.working_dir=working_dir
10
+ def process(A,processor,data,task_name):
11
+ G='meta';F='date';logging.info(f"Processing html report");H=TimeTracker();C=H.get_data()['start']
12
+ if A.config.logo and A.config.logo_file:raise ValueError('logo and logo_file are exclusive.')
13
+ I=A.config.logo;B=None
14
+ if A.config.logo_file:
15
+ D=A.config.logo_file
16
+ if os.path.isabs(D):B=D
17
+ else:
18
+ B=os.path.join(A.working_dir,D)
19
+ if not os.path.isfile(B):
20
+ for J in A.alt_config_dirs:
21
+ B=os.path.join(J,D)
22
+ if os.path.isfile(B):break
23
+ if not os.path.isfile(B):raise FileNotFoundError(f"Derived Logo file '{A.config.logo_file}' not found.")
24
+ K=A.config.title if A.config.title else A.config.title_prefix+' '+task_name;L={'app_name':product_name,'app_version':pkg_version,'app_homepage':pkg_homepage,'app_copyright':f"© {C[F][:4]} {powered_by['name']}. All rights reserved",'app_powered_by':powered_by,'execution_utc_datetime':C['datetime'],'execution_utc_date':C[F],'execution_utc_time':C['time'],'execution_dir':A.working_dir,'title':K,'image':I or get_logo_image_src(B),'config':A.config.html,'config_json':A.config.html.model_dump_json()};E=None
25
+ if A.config.debug:E=A.config.model_dump_json(indent=4)
26
+ return template_render_infolder('index.html',{G:L,'task':data.get(G),'data':{'files':data.get('processor_results')},'debug':E})
@@ -0,0 +1,30 @@
1
+ _A='html'
2
+ import logging,json,os
3
+ from pydantic import BaseModel
4
+ from scilens.config.models import ReportConfig
5
+ from scilens.utils.file import file_remove,json_write,text_write,yaml_write
6
+ from scilens.utils.php import dict_to_php_array
7
+ from scilens.report.html_report import HtmlReport
8
+ class ReportProcessResults(BaseModel):files_created:list[str]=[]
9
+ class Report:
10
+ def __init__(B,working_dir,alt_config_dirs,config,task_name):A=config;B.path=working_dir;B.alt_config_dirs=alt_config_dirs;B.config=A;B.task_name=task_name;B.extensions={'txt':A.output.export_txt,'json':A.output.export_json,'yaml':A.output.export_yaml,_A:A.output.export_html,'py':A.output.export_py,'js':A.output.export_js,'ts':A.output.export_ts,'php':A.output.export_php}
11
+ def _get_file(A,ext):return os.path.join(A.path,f"{A.config.output.filename}.{ext}")
12
+ def process(B,data,processor=None):
13
+ I=False;D=data;E=ReportProcessResults();logging.info(f"Processing report");J=B.config;F=B.extensions;logging.info(f"Cleaning reports")
14
+ for A in F:file_remove(B._get_file(A))
15
+ G=I
16
+ for(A,K)in F.items():
17
+ if K:
18
+ logging.info(f"Creating report {A}");G=True;C=B._get_file(A);H=True
19
+ if A=='txt':text_write(C,str(D))
20
+ elif A=='json':json_write(C,D)
21
+ elif A=='yaml':yaml_write(C,D)
22
+ elif A=='py':text_write(C,'DATA = '+format(D))
23
+ elif A in['js','ts']:text_write(C,'export default '+json.dumps(D))
24
+ elif A=='php':text_write(C,'<?php\nreturn '+dict_to_php_array(D)+';\n')
25
+ elif A==_A:text_write(C,HtmlReport(B.config,B.alt_config_dirs,B.path).process(processor,D,B.task_name))
26
+ else:H=I;logging.error(f"Extension {A} not implemented")
27
+ if H:E.files_created.append(C)
28
+ if J.output.export_html:logging.info(f"Report generated at file://{B._get_file(_A)}")
29
+ if not G:logging.warning(f"No report to process")
30
+ return E
@@ -0,0 +1,8 @@
1
+ import os
2
+ from jinja2 import Template,Environment,FileSystemLoader
3
+ CURRENT_DIR=os.path.dirname(os.path.realpath(__file__))
4
+ TEMPLATE_DIR=os.path.join(CURRENT_DIR,'templates')
5
+ def template_render_path(filepath,data):
6
+ with open(filepath,'r')as A:B=A.read();C=Template(B);return C.render(data)
7
+ def template_render(filename,data):return template_render_path(os.path.join(TEMPLATE_DIR,filename),data)
8
+ def template_render_infolder(filename,data,template_dir=None):A=FileSystemLoader([template_dir or TEMPLATE_DIR]);B=Environment(loader=A);C=B.get_template(filename);return C.render(data)
@@ -0,0 +1,61 @@
1
+ <!-- TITLE -->
2
+ <h1>
3
+ {% if meta.image %}<img height="40" src="{{ meta.image }}"/>{% endif %}
4
+ {{ meta.title }}
5
+ {{ data.name }}
6
+ </h1>
7
+ {% if debug %}
8
+ <pre>
9
+ {{ debug }}
10
+ </pre>
11
+ {% endif %}
12
+ <!-- INFO -->
13
+ <div class="muted">
14
+ Report generated
15
+ on {{ meta.execution_utc_date }} at {{ meta.execution_utc_time }} UTC
16
+ by <a href="{{ meta.app_homepage }}" target="_blank">{{ meta.app_name }} v{{ meta.app_version }}</a>
17
+ </div>
18
+ <!-- DIAGNOSE -->
19
+ <!-- PARAMETERS -->
20
+ <div>Preferences <action data-command="toogle" data-args="section_parameters"></action> Info <action data-command="toogle" data-args="section_info"></action></div>
21
+ <div id="section_parameters" style="display: none;" class="inset">
22
+ <div>Preferences</div>
23
+ <div id="section_parameters_form"></div>
24
+ </div>
25
+ <div id="section_info" style="display: none;" class="inset">
26
+ <div>Info</div>
27
+ <table style="border:none;">
28
+ <tr>
29
+ <td style="vertical-align: top;border:none;">
30
+ <strong>System</strong>
31
+ <table>
32
+ <tr><th>os</th><td>{{ task.system_info.os }}</td></tr>
33
+ <tr><th>arch</th><td>{{ task.system_info.arch }}</td></tr>
34
+ <tr><th>python</th><td>{{ task.system_info.python }}</td></tr>
35
+ <tr><th>python_info</th><td>{{ task.system_info.python_info }}</td></tr>
36
+ </table>
37
+ </td>
38
+ <td style="vertical-align: top;border:none;">
39
+ <strong>Task Process</strong>
40
+ <table>
41
+ <tr><th>Processor</th><td>{{ task.task_info.processor }}</td></tr>
42
+ <tr><th>Start</th><td>{{ task.task_info.process_time.start.datetime }}</td></tr>
43
+ <tr><th>End</th><td>{{ task.task_info.process_time.end.datetime }}</td></tr>
44
+ </table>
45
+ </td>
46
+ </tr>
47
+ </table>
48
+
49
+ </div>
50
+
51
+
52
+
53
+ <div id="tabs" class="tabs noselect" style="display: none;">
54
+ <div class="tab active" onclick="actions.click_tab(event, 0)">Summary</div>
55
+ {% for file in data.files %}
56
+ {% if not file.skipped %}
57
+ <div class="tab {{ ns.statuses[loop.index] }}" onclick="actions.click_tab(event, {{ loop.index }})"><span class="">{{ file.name }}</span></div>
58
+ {% endif %}
59
+ {% endfor %}
60
+ <div class="tabend"></div>
61
+ </div>
@@ -0,0 +1,11 @@
1
+ <div class="footer muted">
2
+ <div class="py-1">
3
+ <a href="{{ meta.app_homepage }}" target="_blank">{{ meta.app_name }} - {{ meta.app_version }}</a>
4
+ </div>
5
+ <div class="py-1">
6
+ Powered by <a href="{{ meta.app_powered_by.url }}" target="_blank">{{ meta.app_powered_by.name }}</a>
7
+ </div>
8
+ <div class="py-1">
9
+ {{ meta.app_copyright }}
10
+ </div>
11
+ </div>