ixbrl-viewer 1.4.15__py3-none-any.whl → 1.4.16__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.

Potentially problematic release.


This version of ixbrl-viewer might be problematic. Click here for more details.

@@ -9,18 +9,20 @@ import sys
9
9
  import tempfile
10
10
  import traceback
11
11
  from optparse import OptionGroup, OptionParser
12
+ from pathlib import Path
12
13
 
13
14
  from arelle import Cntlr
14
15
  from arelle.LocalViewer import LocalViewer
15
16
  from arelle.ModelDocument import Type
17
+ from arelle.UrlUtil import isHttpUrl
16
18
  from arelle.webserver.bottle import static_file
17
19
 
18
- from .constants import CONFIG_FEATURE_PREFIX, CONFIG_LAUNCH_ON_LOAD, \
19
- CONFIG_SCRIPT_URL, DEFAULT_LAUNCH_ON_LOAD, DEFAULT_OUTPUT_NAME, \
20
- DEFAULT_JS_FILENAME, DEFAULT_VIEWER_PATH, FEATURE_CONFIGS
20
+ from .constants import CONFIG_COPY_SCRIPT, CONFIG_FEATURE_PREFIX, CONFIG_LAUNCH_ON_LOAD, \
21
+ CONFIG_SCRIPT_URL, DEFAULT_COPY_SCRIPT, DEFAULT_LAUNCH_ON_LOAD, DEFAULT_OUTPUT_NAME, \
22
+ DEFAULT_JS_FILENAME, DEFAULT_VIEWER_PATH, ERROR_MESSAGE_CODE, \
23
+ EXCEPTION_MESSAGE_CODE, FEATURE_CONFIGS
21
24
  from .iXBRLViewer import IXBRLViewerBuilder, IXBRLViewerBuilderError
22
25
 
23
-
24
26
  #
25
27
  # GUI operation:
26
28
  #
@@ -54,7 +56,18 @@ def iXBRLViewerCommandLineOptionExtender(parser, *args, **kwargs):
54
56
  action="store",
55
57
  dest="viewerURL",
56
58
  default=DEFAULT_VIEWER_PATH,
57
- help="Specify the URL to ixbrlviewer.js")
59
+ help="A filepath or URL to the iXBRL Viewer JavaScript file which will be downloaded or copied into the output directory."
60
+ " If a relative filepath is to be copied it will be resolved relative to the current working directory."
61
+ " If '--viewer-no-copy-script' is used, the '--viewer-url' file will not be copied or downloaded and instead directly referenced from the HTML file."
62
+ " Examples: 'customViewerScript.js', '/path/to/ixbrlviewer.js', 'https://example.com/ixbrlviewer.js'."
63
+ f" The default value is '{DEFAULT_JS_FILENAME}'.")
64
+ parser.add_option("--viewer-no-copy-script",
65
+ action="store_true",
66
+ dest="viewerNoCopyScript",
67
+ default=False,
68
+ help="Prevent copying the iXBRL Viewer's JavaScript file from '--viewer-url' into the output directory."
69
+ " If used, the iXBRL Viewer HTML file will reference the '--viewer-url' directly."
70
+ " It must be a valid script location at the time the viewer is opened in a browser.")
58
71
  parser.add_option("--viewer-validation-messages",
59
72
  dest="validationMessages",
60
73
  action="store_true",
@@ -102,48 +115,63 @@ def generateViewer(
102
115
  zipViewerOutput: bool = False,
103
116
  features: list[str] | None = None,
104
117
  packageDownloadURL: str | None = None,
105
- copyScript = True
118
+ copyScript: bool = True,
106
119
  ) -> None:
107
120
  """
108
- Generate and save a viewer at the given destination (file, directory, or in-memory file) with the given viewer URL.
109
- If the viewer URL is a location on the local file system, a copy will be placed included in the output destination.
121
+ Generate and save an iXBRL viewer at the given destination (file, directory, or in-memory file) with the given viewer script URL.
122
+ If copyScript is True the viewer script will be copied into the output destination.
110
123
  :param cntlr: The arelle controller that contains the model to be included in the viewer
111
124
  :param saveViewerDest: The target that viewer data/files will be written to (path to file or directory, or a file object itself).
112
- :param viewerURL: The filepath or URL location of the viewer script.
125
+ :param viewerURL: The filepath or URL location of the iXBRL Viewer JavaScript file.
113
126
  :param showValidationMessages: True if validation messages should be shown in the viewer.
114
127
  :param useStubViewer: True if the stub viewer should be used.
115
128
  :param zipViewerOutput: True if the destination is a zip archive.
116
- :param features: List of feature names to enable via generated JSON data.
129
+ :param features: Optional list of features to enable.
130
+ :param packageDownloadURL: Optional URL to use as the report package download URL.
131
+ :param copyScript: Controls if the script referenced by viewerURL is copied into the output directory, or directly set as the 'src' value of the script tag in the HTML iXBRL Viewer.
117
132
  """
118
133
  # extend XBRL-loaded run processing for this option
119
- if saveViewerDest is None:
134
+ abortGenerationMsg = "Skipping iXBRL Viewer generation."
135
+ if not saveViewerDest:
136
+ cntlr.addToLog(f"iXBRL Viewer destination not provided. {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
120
137
  return
121
- if (cntlr.modelManager is None
122
- or len(cntlr.modelManager.loadedModelXbrls) == 0
138
+
139
+ if not viewerURL:
140
+ cntlr.addToLog(f"iXBRL Viewer script not provided. {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
141
+ return
142
+
143
+ if (cntlr.modelManager is None
144
+ or len(cntlr.modelManager.loadedModelXbrls) == 0
123
145
  or any(not mx.modelDocument for mx in cntlr.modelManager.loadedModelXbrls)):
124
- cntlr.addToLog("No taxonomy loaded.")
146
+ cntlr.addToLog(f"No taxonomy loaded. {abortGenerationMsg}", messageCode=ERROR_MESSAGE_CODE)
125
147
  return
126
- modelXbrl = cntlr.modelManager.modelXbrl
127
- if modelXbrl.modelDocument.type not in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET):
128
- cntlr.addToLog("No inline XBRL document loaded.")
148
+ if cntlr.modelManager.modelXbrl.modelDocument.type not in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET):
149
+ cntlr.addToLog(f"No inline XBRL document loaded. {abortGenerationMsg}", messageCode=ERROR_MESSAGE_CODE)
129
150
  return
151
+
130
152
  copyScriptPath = None
131
- if isinstance(saveViewerDest, str):
132
- # Note on URLs: Rather than rely on logic to determine if the input is a file
133
- # path or web address, we can allow web addresses to be considered relative paths.
134
- # Unless the URL happens to resolve to an existing file on the local filesystem,
135
- # it will skip this step and pass through into the viewer as expected.
136
- if os.path.isabs(viewerURL):
137
- viewerAbsolutePath = viewerURL
138
- else:
139
- viewerAbsolutePath = getAbsoluteViewerPath(saveViewerDest, viewerURL)
140
-
141
- if copyScript and os.path.isfile(viewerAbsolutePath):
142
- # The script was found on the local file system and will be copied into the
143
- # destination directory, so the local path (just the basename) of viewerURL should
144
- # be passed to the script tag
145
- copyScriptPath = viewerURL
146
- viewerURL = os.path.basename(viewerURL)
153
+ if copyScript:
154
+ originalViewerURL = viewerURL
155
+ viewerPath = None
156
+ if isHttpUrl(originalViewerURL):
157
+ cacheScript = cntlr.webCache.getfilename(originalViewerURL)
158
+ if cacheScript and (cacheScriptPath := Path(cacheScript)).is_file():
159
+ viewerPath = cacheScriptPath
160
+ else:
161
+ downloadFailedErrorMessage = f"Unable to download iXBRL Viewer script '{originalViewerURL}'."
162
+ if cntlr.webCache.workOffline:
163
+ downloadFailedErrorMessage += " Disable offline mode and try again."
164
+ cntlr.addToLog(f"{downloadFailedErrorMessage} {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
165
+ return
166
+
167
+ if not viewerPath:
168
+ viewerPath = Path(viewerURL)
169
+ copyScriptPath = viewerPath.resolve()
170
+ viewerURL = viewerPath.name
171
+ if not viewerPath.is_file():
172
+ cntlr.addToLog(f"iXBRL Viewer script not found at '{viewerPath}'. {abortGenerationMsg}", messageCode=EXCEPTION_MESSAGE_CODE)
173
+ return
174
+
147
175
  try:
148
176
  viewerBuilder = IXBRLViewerBuilder(cntlr.modelManager.loadedModelXbrls)
149
177
  if features:
@@ -155,20 +183,8 @@ def generateViewer(
155
183
  except IXBRLViewerBuilderError as ex:
156
184
  print(ex)
157
185
  except Exception as ex:
158
- cntlr.addToLog("Exception {} \nTraceback {}".format(ex, traceback.format_tb(sys.exc_info()[2])))
159
-
160
-
161
- def getAbsoluteViewerPath(saveViewerPath: str, relativeViewerPath: str) -> str:
162
- """
163
- Generate a path to the viewer script given the save destination path as a starting point.
164
- :param saveViewerPath: Path to file or directory where viewer output will be saved.
165
- :param relativeViewerPath: Path to save destination relative to viewer save path.
166
- :return: An absolute file path to the viewer.
167
- """
168
- saveViewerDir = saveViewerPath
169
- if os.path.isfile(saveViewerDir):
170
- saveViewerDir = os.path.dirname(os.path.join(os.getcwd(), saveViewerDir))
171
- return os.path.join(saveViewerDir, relativeViewerPath)
186
+ tb = traceback.format_tb(sys.exc_info()[2])
187
+ cntlr.addToLog(f"Exception {ex} \nTraceback {tb}", messageCode=EXCEPTION_MESSAGE_CODE)
172
188
 
173
189
 
174
190
  def getFeaturesFromOptions(options: argparse.Namespace | OptionParser):
@@ -181,35 +197,37 @@ def getFeaturesFromOptions(options: argparse.Namespace | OptionParser):
181
197
 
182
198
  def iXBRLViewerCommandLineXbrlRun(cntlr, options, *args, **kwargs):
183
199
  generateViewer(
184
- cntlr,
185
- options.saveViewerDest or kwargs.get("responseZipStream"),
186
- options.viewerURL,
187
- options.validationMessages,
188
- options.useStubViewer,
189
- options.zipViewerOutput,
190
- getFeaturesFromOptions(options),
191
- options.packageDownloadURL,
200
+ cntlr=cntlr,
201
+ saveViewerDest=options.saveViewerDest or kwargs.get("responseZipStream"),
202
+ viewerURL=options.viewerURL,
203
+ copyScript=not options.viewerNoCopyScript,
204
+ showValidationMessages=options.validationMessages,
205
+ useStubViewer=options.useStubViewer,
206
+ zipViewerOutput=options.zipViewerOutput,
207
+ features=getFeaturesFromOptions(options),
208
+ packageDownloadURL=options.packageDownloadURL,
192
209
  )
193
210
 
194
211
 
195
212
  def iXBRLViewerSaveCommand(cntlr):
196
213
  from .ui import SaveViewerDialog
197
214
  if cntlr.modelManager is None or cntlr.modelManager.modelXbrl is None:
198
- cntlr.addToLog("No document loaded.")
215
+ cntlr.addToLog("No document loaded.", messageCode=ERROR_MESSAGE_CODE)
199
216
  return
200
217
  modelXbrl = cntlr.modelManager.modelXbrl
201
218
  if modelXbrl.modelDocument.type not in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET):
202
- cntlr.addToLog("No inline XBRL document loaded.")
219
+ cntlr.addToLog("No inline XBRL document loaded.", messageCode=ERROR_MESSAGE_CODE)
203
220
  return
204
221
  dialog = SaveViewerDialog(cntlr)
205
222
  dialog.render()
206
223
  if dialog.accepted and dialog.filename():
207
224
  generateViewer(
208
- cntlr,
209
- dialog.filename(),
210
- dialog.scriptUrl() or DEFAULT_VIEWER_PATH,
225
+ cntlr=cntlr,
226
+ saveViewerDest=dialog.filename(),
227
+ viewerURL=dialog.scriptUrl() or DEFAULT_VIEWER_PATH,
228
+ copyScript=dialog.copyScript(),
211
229
  zipViewerOutput=dialog.zipViewerOutput(),
212
- features=dialog.features()
230
+ features=dialog.features(),
213
231
  )
214
232
 
215
233
 
@@ -285,18 +303,20 @@ def guiRun(cntlr, modelXbrl, attach, *args, **kwargs):
285
303
  if cntlr.config.setdefault(f'{CONFIG_FEATURE_PREFIX}{c.key}', False)
286
304
  ]
287
305
  generateViewer(
288
- cntlr,
306
+ cntlr=cntlr,
289
307
  saveViewerDest=tempViewer.name,
290
308
  viewerURL=cntlr.config.get(CONFIG_SCRIPT_URL) or DEFAULT_VIEWER_PATH,
309
+ copyScript=cntlr.config.get(CONFIG_COPY_SCRIPT, DEFAULT_COPY_SCRIPT),
291
310
  useStubViewer=True,
292
- features=features
311
+ features=features,
293
312
  )
294
- localViewer = iXBRLViewerLocalViewer("iXBRL Viewer", os.path.dirname(__file__))
295
- localhost = localViewer.init(cntlr, tempViewer.name)
296
- webbrowser.open(f'{localhost}/{viewer_file_name}')
313
+ if Path(tempViewer.name, viewer_file_name).exists():
314
+ localViewer = iXBRLViewerLocalViewer("iXBRL Viewer", os.path.dirname(__file__))
315
+ localhost = localViewer.init(cntlr, tempViewer.name)
316
+ webbrowser.open(f'{localhost}/{viewer_file_name}')
297
317
  except Exception as ex:
298
318
  modelXbrl.error(
299
- "viewer:exception",
319
+ EXCEPTION_MESSAGE_CODE,
300
320
  "Exception %(exception)s \nTraceback %(traceback)s",
301
321
  modelObject=modelXbrl, exception=ex, traceback=traceback.format_tb(sys.exc_info()[2])
302
322
  )
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.4.15'
16
- __version_tuple__ = version_tuple = (1, 4, 15)
15
+ __version__ = version = '1.4.16'
16
+ __version_tuple__ = version_tuple = (1, 4, 16)
@@ -7,11 +7,17 @@ from .featureConfig import FeatureConfig
7
7
  CONFIG_FEATURE_PREFIX = 'iXBRLViewerFeature_'
8
8
  CONFIG_FILE_DIRECTORY = 'iXBRLViewerFileDir'
9
9
  CONFIG_LAUNCH_ON_LOAD = 'iXBRLViewerLaunchOnLoad'
10
+ CONFIG_COPY_SCRIPT = 'iXBRLViewerCopyScript'
10
11
  CONFIG_OUTPUT_FILE = 'iXBRLViewerOutputFile'
11
12
  CONFIG_SCRIPT_URL = 'iXBRLViewerScriptURL'
12
13
  CONFIG_ZIP_OUTPUT = 'iXBRLViewerZipOutput'
13
14
 
15
+ ERROR_MESSAGE_CODE = 'viewer:error'
16
+ EXCEPTION_MESSAGE_CODE = 'viewer:exception'
17
+ INFO_MESSAGE_CODE = 'viewer:info'
18
+
14
19
  DEFAULT_LAUNCH_ON_LOAD = True
20
+ DEFAULT_COPY_SCRIPT = True
15
21
  DEFAULT_OUTPUT_NAME = 'ixbrlviewer.html'
16
22
  DEFAULT_JS_FILENAME = 'ixbrlviewer.js'
17
23
  DEFAULT_VIEWER_PATH = os.path.join(os.path.dirname(__file__), "viewer", "dist", DEFAULT_JS_FILENAME)
@@ -12,6 +12,7 @@ import urllib.parse
12
12
  import zipfile
13
13
  from collections import defaultdict
14
14
  from copy import deepcopy
15
+ from pathlib import Path
15
16
 
16
17
  import pycountry
17
18
  from arelle import XbrlConst
@@ -23,7 +24,7 @@ from arelle.UrlUtil import isHttpUrl
23
24
  from arelle.ValidateXbrlCalcs import inferredDecimals
24
25
  from lxml import etree
25
26
 
26
- from .constants import DEFAULT_OUTPUT_NAME, DEFAULT_VIEWER_PATH, FEATURE_CONFIGS
27
+ from .constants import DEFAULT_JS_FILENAME, DEFAULT_OUTPUT_NAME, ERROR_MESSAGE_CODE, FEATURE_CONFIGS, INFO_MESSAGE_CODE
27
28
  from .xhtmlserialize import XHTMLSerializer
28
29
 
29
30
 
@@ -352,7 +353,7 @@ class IXBRLViewerBuilder:
352
353
  if child.tag == '{http://www.w3.org/1999/xhtml}body':
353
354
  for body_child in child:
354
355
  if body_child.tag == '{http://www.w3.org/1999/xhtml}script' and body_child.get('type','') == 'application/x.ixbrl-viewer+json':
355
- self.logger_model.error("viewer:error", "File already contains iXBRL viewer")
356
+ self.logger_model.error(ERROR_MESSAGE_CODE, "File already contains iXBRL viewer")
356
357
  return False
357
358
 
358
359
  child.append(etree.Comment("BEGIN IXBRL VIEWER EXTENSIONS"))
@@ -395,7 +396,7 @@ class IXBRLViewerBuilder:
395
396
 
396
397
  def createViewer(
397
398
  self,
398
- scriptUrl: str = DEFAULT_VIEWER_PATH,
399
+ scriptUrl: str = DEFAULT_JS_FILENAME,
399
400
  useStubViewer: bool = False,
400
401
  showValidations: bool = True,
401
402
  packageDownloadURL: str | None = None,
@@ -430,7 +431,7 @@ class IXBRLViewerBuilder:
430
431
  self.currentTargetReport["rels"] = self.getRelationships(report)
431
432
 
432
433
  docSetFiles = None
433
- report.info("viewer:info", "Creating iXBRL viewer (%d of %d)" % (n+1, len(self.reports)))
434
+ report.info(INFO_MESSAGE_CODE, "Creating iXBRL viewer (%d of %d)" % (n+1, len(self.reports)))
434
435
  if report.modelDocument.type == Type.INLINEXBRLDOCUMENTSET:
435
436
  # Sort by object index to preserve order in which files were specified.
436
437
  xmlDocsByFilename = {
@@ -535,7 +536,7 @@ class iXBRLViewer:
535
536
  def addFilingDoc(self, filingDocuments):
536
537
  self.filingDocuments = filingDocuments
537
538
 
538
- def save(self, destination: io.BytesIO | str, zipOutput: bool = False, copyScriptPath: str | None = None):
539
+ def save(self, destination: io.BytesIO | str, zipOutput: bool = False, copyScriptPath: Path | None = None):
539
540
  """
540
541
  Save the iXBRL viewer.
541
542
  :param destination: The target that viewer data/files will be written to (path to file/directory, or a file object itself).
@@ -553,15 +554,15 @@ class iXBRLViewer:
553
554
  fileMode = 'w'
554
555
  elif destination.endswith(os.sep):
555
556
  # Looks like a directory, but isn't one
556
- self.logger_model.error("viewer:error", "Directory %s does not exist" % destination)
557
+ self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % destination)
557
558
  return
558
559
  elif not os.path.isdir(os.path.dirname(os.path.abspath(destination))):
559
560
  # Directory part of filename doesn't exist
560
- self.logger_model.error("viewer:error", "Directory %s does not exist" % os.path.dirname(os.path.abspath(destination)))
561
+ self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % os.path.dirname(os.path.abspath(destination)))
561
562
  return
562
563
  elif not destination.endswith('.zip'):
563
564
  # File extension isn't a zip
564
- self.logger_model.error("viewer:error", "File extension %s is not a zip" % os.path.splitext(destination)[0])
565
+ self.logger_model.error(ERROR_MESSAGE_CODE, "File extension %s is not a zip" % os.path.splitext(destination)[0])
565
566
  return
566
567
  else:
567
568
  file = destination
@@ -569,57 +570,56 @@ class iXBRLViewer:
569
570
 
570
571
  with zipfile.ZipFile(file, fileMode, zipfile.ZIP_DEFLATED, True) as zout:
571
572
  for f in self.files:
572
- self.logger_model.info("viewer:info", "Saving in output zip %s" % f.filename)
573
+ self.logger_model.info(INFO_MESSAGE_CODE, "Saving in output zip %s" % f.filename)
573
574
  with zout.open(f.filename, "w") as fout:
574
575
  writer = XHTMLSerializer(fout)
575
576
  writer.serialize(f.xmlDocument)
576
577
  if self.filingDocuments:
577
578
  filename = os.path.basename(self.filingDocuments)
578
- self.logger_model.info("viewer:info", "Writing %s" % filename)
579
+ self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
579
580
  zout.write(self.filingDocuments, filename)
580
581
  if copyScriptPath is not None:
581
- scriptSrc = os.path.join(destination, copyScriptPath)
582
- self.logger_model.info("viewer:info", "Writing script from %s" % scriptSrc)
583
- zout.write(scriptSrc, os.path.basename(copyScriptPath))
582
+ self.logger_model.info(INFO_MESSAGE_CODE, f"Writing script from {copyScriptPath}")
583
+ zout.write(copyScriptPath, copyScriptPath.name)
584
584
  elif os.path.isdir(destination):
585
585
  # If output is a directory, write each file in the doc set to that
586
586
  # directory using its existing filename
587
587
  for f in self.files:
588
588
  filename = os.path.join(destination, f.filename)
589
- self.logger_model.info("viewer:info", "Writing %s" % filename)
589
+ self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
590
590
  with open(filename, "wb") as fout:
591
591
  writer = XHTMLSerializer(fout)
592
592
  writer.serialize(f.xmlDocument)
593
593
  if self.filingDocuments:
594
594
  filename = os.path.basename(self.filingDocuments)
595
- self.logger_model.info("viewer:info", "Writing %s" % filename)
595
+ self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
596
596
  shutil.copy2(self.filingDocuments, os.path.join(destination, filename))
597
597
  if copyScriptPath is not None:
598
- self._copyScript(destination, copyScriptPath)
598
+ self._copyScript(Path(destination), copyScriptPath)
599
599
  else:
600
600
  if len(self.files) > 1:
601
- self.logger_model.error("viewer:error", "More than one file in input, but output is not a directory")
601
+ self.logger_model.error(ERROR_MESSAGE_CODE, "More than one file in input, but output is not a directory")
602
602
  elif destination.endswith(os.sep):
603
603
  # Looks like a directory, but isn't one
604
- self.logger_model.error("viewer:error", "Directory %s does not exist" % destination)
604
+ self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % destination)
605
605
  elif not os.path.isdir(os.path.dirname(os.path.abspath(destination))):
606
606
  # Directory part of filename doesn't exist
607
- self.logger_model.error("viewer:error", "Directory %s does not exist" % os.path.dirname(os.path.abspath(destination)))
607
+ self.logger_model.error(ERROR_MESSAGE_CODE, "Directory %s does not exist" % os.path.dirname(os.path.abspath(destination)))
608
608
  else:
609
- self.logger_model.info("viewer:info", "Writing %s" % destination)
609
+ self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % destination)
610
610
  with open(destination, "wb") as fout:
611
611
  writer = XHTMLSerializer(fout)
612
612
  writer.serialize(self.files[0].xmlDocument)
613
613
  if self.filingDocuments:
614
614
  filename = os.path.basename(self.filingDocuments)
615
- self.logger_model.info("viewer:info", "Writing %s" % filename)
615
+ self.logger_model.info(INFO_MESSAGE_CODE, "Writing %s" % filename)
616
616
  shutil.copy2(self.filingDocuments, os.path.join(os.path.dirname(destination), filename))
617
617
  if copyScriptPath is not None:
618
- outDirectory = os.path.dirname(os.path.join(os.getcwd(), destination))
618
+ outDirectory = Path(destination).parent
619
619
  self._copyScript(outDirectory, copyScriptPath)
620
620
 
621
- def _copyScript(self, directory: str, scriptPath: str):
622
- scriptSrc = os.path.join(directory, scriptPath)
623
- scriptDest = os.path.join(directory, os.path.basename(scriptPath))
624
- self.logger_model.info("viewer:info", "Copying script from %s to %s" % (scriptSrc, scriptDest))
625
- shutil.copy2(scriptSrc, scriptDest)
621
+ def _copyScript(self, destDirectory: Path, scriptPath: Path):
622
+ scriptDest = destDirectory / scriptPath.name
623
+ if scriptPath != scriptDest:
624
+ self.logger_model.info(INFO_MESSAGE_CODE, f"Copying script from {scriptDest} to {scriptDest}.")
625
+ shutil.copy2(scriptPath, scriptDest)
iXBRLViewerPlugin/ui.py CHANGED
@@ -8,9 +8,9 @@ except ImportError:
8
8
 
9
9
  import os
10
10
 
11
- from .constants import CONFIG_FEATURE_PREFIX, CONFIG_FILE_DIRECTORY, CONFIG_LAUNCH_ON_LOAD, \
12
- CONFIG_OUTPUT_FILE, CONFIG_SCRIPT_URL, CONFIG_ZIP_OUTPUT, DEFAULT_LAUNCH_ON_LOAD, \
13
- GUI_FEATURE_CONFIGS
11
+ from .constants import CONFIG_COPY_SCRIPT, CONFIG_FEATURE_PREFIX, CONFIG_FILE_DIRECTORY, \
12
+ CONFIG_LAUNCH_ON_LOAD, CONFIG_OUTPUT_FILE, CONFIG_SCRIPT_URL, CONFIG_ZIP_OUTPUT, \
13
+ DEFAULT_COPY_SCRIPT, DEFAULT_LAUNCH_ON_LOAD, GUI_FEATURE_CONFIGS
14
14
 
15
15
  UNSET_SCRIPT_URL = ''
16
16
 
@@ -30,6 +30,8 @@ class BaseViewerDialog(Toplevel):
30
30
  self._features[featureConfig.key] = featureVar
31
31
  self._scriptUrl = StringVar()
32
32
  self._scriptUrl.set(self.cntlr.config.setdefault(CONFIG_SCRIPT_URL, UNSET_SCRIPT_URL))
33
+ self._copyScript = BooleanVar()
34
+ self._copyScript.set(self.cntlr.config.setdefault(CONFIG_COPY_SCRIPT, DEFAULT_COPY_SCRIPT))
33
35
 
34
36
  def addButtons(self, frame: Frame, x: int, y: int) -> int:
35
37
  """
@@ -62,6 +64,12 @@ class BaseViewerDialog(Toplevel):
62
64
  scriptUrlLabel.grid(row=y, column=0, sticky=W, pady=3, padx=3)
63
65
  scriptUrlEntry.grid(row=y, column=1, columnspan=2, sticky=EW, pady=3, padx=3)
64
66
 
67
+ y += 1
68
+ copyScriptCheckbutton = Checkbutton(frame, text="Copy Script", variable=self._copyScript, onvalue=True, offvalue=False)
69
+ copyScriptLabel = Label(frame, text="Copy the iXBRL Viewer script into the output directory.")
70
+ copyScriptCheckbutton.grid(row=y, column=0, pady=3, padx=3, sticky=W)
71
+ copyScriptLabel.grid(row=y, column=1, columnspan=3, pady=3, padx=3, sticky=W)
72
+
65
73
  y += 1
66
74
  featuresLabel = Label(frame, text="Generate with optional features:")
67
75
  featuresLabel.grid(row=y, column=0, columnspan=2, pady=3, padx=3, sticky=W)
@@ -130,6 +138,9 @@ class BaseViewerDialog(Toplevel):
130
138
  self.grab_set()
131
139
  self.wait_window(self)
132
140
 
141
+ def copyScript(self):
142
+ return self._copyScript.get()
143
+
133
144
  def features(self):
134
145
  # Return list of feature keys with corresponding BooleanVar is set to True
135
146
  return [feature for feature, value in self._features.items() if value.get()]
@@ -238,6 +249,7 @@ class SettingsDialog(BaseViewerDialog):
238
249
  """
239
250
  self.cntlr.config[CONFIG_LAUNCH_ON_LOAD] = self._launchOnLoad.get()
240
251
  self.cntlr.config[CONFIG_SCRIPT_URL] = self._scriptUrl.get()
252
+ self.cntlr.config[CONFIG_COPY_SCRIPT] = self._copyScript.get()
241
253
  for key, var in self._features.items():
242
254
  self.cntlr.config[f'{CONFIG_FEATURE_PREFIX}{key}'] = var.get()
243
255
  self.cntlr.saveConfig()
@@ -249,5 +261,6 @@ class SettingsDialog(BaseViewerDialog):
249
261
  """
250
262
  self._launchOnLoad.set(DEFAULT_LAUNCH_ON_LOAD)
251
263
  self._scriptUrl.set(UNSET_SCRIPT_URL)
264
+ self._copyScript.set(DEFAULT_COPY_SCRIPT)
252
265
  for featureConfig in GUI_FEATURE_CONFIGS:
253
266
  self._features[featureConfig.key].set(featureConfig.guiDefault)