diff --git a/src/spec2nexus/plugins/spec_common.py b/src/spec2nexus/plugins/spec_common.py
index 4dde007..c2619b5 100644
--- a/src/spec2nexus/plugins/spec_common.py
+++ b/src/spec2nexus/plugins/spec_common.py
@@ -89,6 +89,14 @@ class SPEC_Epoch(ControlLineHandler):
     # do NOT add E to scan_attributes_defined
     
     def process(self, buf, sdf_object, *args, **kws):
+        matches = [
+            h 
+            for h in sdf_object.headers
+            if h.raw.strip() == buf.strip()
+            ]
+        if len(matches) > 0:
+            # this header exists, nothing to do
+            return
         header = SpecDataFileHeader(buf, parent=sdf_object)
         line = buf.splitlines()[0].strip()
         if line.find(".") > -1:
@@ -224,14 +232,46 @@ class SPEC_Scan(ControlLineHandler):
         else:
             header = sdf.headers[-1]    # pick the most recent header
 
+        matches = [
+            (k, s) 
+            for k, s in sdf.scans.items()
+            if s.raw.strip() == part.strip()
+            ]
+
+        if len(matches) > 0 and sdf.last_scan is not None:
+            if sdf.getScan(sdf.last_scan).raw != part:
+                return
+
         scan = SpecDataFileScan(header, part, parent=sdf)
+        if sdf.last_scan is not None:
+            # We know that `part` does not match any existing scan.
+            # Do the first few lines match (#S and #D in particular)?
+            # If so, then that scan has been updated with more data.
+            def beginning(buf):
+                "return a string with first few lines of buf"
+                return "\n".join(
+                    [
+                        line
+                        for line in buf.strip().splitlines()[:5]
+                        if line.startswith("#")
+                    ]
+                )
+
+            # TODO: replace beginning() with new algorithm
+            # if new_scan.raw.startswith(last_scan.raw), that's it!
+
+            if beginning(part) == beginning(sdf.getScan(sdf.last_scan).raw):
+                # remove the last scan
+                del sdf.scans[sdf.last_scan]
+                sdf.last_scan = None
+
         scan.S = strip_first_word(part.splitlines()[0].strip())
         scan.scanNum = scan.S.split()[0]
         scan.scanCmd = strip_first_word(scan.S)
         
         if scan.scanNum in sdf.scans:
             # Before raising an exception, 
-            #    Check for duplicate and create alternate name
+            #    Check for duplicate scan number and create alternate name
             #    write as "%d.%d" % (scan.scanNum, index+1) 
             #    where index is the lowest integer in 
             #    range(really big) that is not already in use.
diff --git a/src/spec2nexus/spec.py b/src/spec2nexus/spec.py
index a236888..1ef4c78 100644
--- a/src/spec2nexus/spec.py
+++ b/src/spec2nexus/spec.py
@@ -216,17 +216,18 @@ class SpecDataFile(object):
 
     .. autosummary::
 
-        ~update_available
         ~dissect_file
-        ~read
-        ~getScan
-        ~getScanNumbers
-        ~getScanNumbersChronological
-        ~getMinScanNumber
-        ~getMaxScanNumber
         ~getFirstScanNumber
         ~getLastScanNumber
+        ~getMaxScanNumber
+        ~getMinScanNumber
+        ~getScan
         ~getScanCommands
+        ~getScanNumbers
+        ~getScanNumbersChronological
+        ~read
+        ~refresh
+        ~update_available
 
     """
 
@@ -243,7 +244,7 @@ class SpecDataFile(object):
         self.readOK = -1
         self.last_scan = None
         self.mtime = 0
-        self.num_lines = 0
+        self.filesize = 0
 
         if filename is not None:
             if not os.path.exists(filename):
@@ -266,8 +267,57 @@ class SpecDataFile(object):
         
         Reference file modification time is stored *after* 
         file is read in :meth:`read()` method.
+        
+        EXAMPLE USAGE
+
+        Open the SPEC data file (example):
+        
+            sdf = spec.SpecDataFile(filename)
+        
+        then, monitor (continuing example):
+        
+            if sdf.update_available:
+                myLastScan = sdf.last_scan
+                sdf.read()
+                plot_scan_and_newer(myLastScan)    # new method
+                myLastScan = sdf.last_scan
+        
+        .. note: The previous last_scan is reprocessed since 
+           that scan may not have been complete when the file 
+           was read() previously.
+
+        """
+        same_mtime = self.mtime == os.path.getmtime(self.fileName)
+        same_size = self.filesize == os.path.getsize(self.fileName)
+        identical = same_mtime and same_size
+        return not identical
+
+    def refresh(self):
         """
-        return self.mtime != os.path.getmtime(self.fileName)
+        update (refresh) the content if the file is updated
+    
+        returns previous last_scan or None if file not updated
+
+        .. caution:  previous last_scan must be re-created if updated
+        
+           After calling :meth:`refresh()`, any client
+           with an object of the previous last scan
+           should get a new object with the update data.  
+           
+           EXAMPLE::
+           
+               scan = sdf.getScan(42)
+               checkpoint = sdf.refresh()
+               if checkpoint is not None:
+                   scan = sdf.getScan(checkpoint)    # get updates
+
+        """
+        if self.update_available:
+            previous_scan = self.last_scan
+            
+            self.read()
+            return previous_scan
+        return None
 
     def _read_file_(self, spec_file_name):
         """Reads a spec data file"""
@@ -302,7 +352,6 @@ class SpecDataFile(object):
         
         """
         buf = self._read_file_(self.fileName).splitlines()
-        self.num_lines = len(buf)
         
         sections, block = [], []
         
@@ -343,6 +392,7 @@ class SpecDataFile(object):
             self.specFile = self.fileName
 
         self.last_scan = self.getLastScanNumber()
+        self.filesize = os.path.getsize(self.fileName)
         self.mtime = os.path.getmtime(self.fileName)
     
     def getScan(self, scan_number=0):
diff --git a/src/spec2nexus/specplot_gallery.py b/src/spec2nexus/specplot_gallery.py
index af9a827..b6ed3c7 100755
--- a/src/spec2nexus/specplot_gallery.py
+++ b/src/spec2nexus/specplot_gallery.py
@@ -19,18 +19,17 @@ read a list of SPEC data files (or directories) and plot images of all scans
     ~PlotSpecFileScans
     ~Cache_File_Mtime
     ~datePath
-    ~get_SpecFileDate
-    ~get_file_mtime
+    ~getSpecFileDate
     ~needToMakePlot
     ~timestamp
-    ~build_index_html
+    ~buildIndexHtml
     ~logger
 
 .. rubric:: RESULT
 
 The images are stored in files within a directory structure
 that is organized chronologically,
-such as: ``yyyy/mm/spec_file/s1.png``.
+such as: ``yyyy/mm/spec_file/s1.svg``.
 The root of the directory is either specified by the 
 command line ``-d`` option or  defaults to the current 
 working directory.  The ``yyyy/mm`` (year and month) are 
@@ -57,6 +56,7 @@ To identify and stop all processes of this program::
 
 
 import datetime
+import json
 import logging
 import os
 import shutil
@@ -71,7 +71,8 @@ except NameError:
     FileNotFoundError = IOError     # py27 compatibility
 
 
-MTIME_CACHE_FILE = 'mtime_cache.txt'
+MTIME_CACHE_FILE = 'mtime_cache.json'
+PLOT_TYPE = ".svg"
 HTML_INDEX_FILE = 'index.html'
 DOC_URL = 'http://spec2nexus.readthedocs.io/en/latest/specplot_gallery.html'
 
@@ -90,6 +91,15 @@ class PlotSpecFileScans(object):
     
     :param [str] filelist: list of SPEC data files to be checked
     :param str plotDir: name of base directory to store output image thumbnails
+
+    .. autosummary::
+    
+        ~specFileUpdated
+        ~plot_all_scans
+        ~getPlotDir
+        ~getBaseDir
+        ~href_format
+
     """
 
     def __init__(self, filelist, plotDir = None, reverse_chronological = False):
@@ -100,32 +110,18 @@ class PlotSpecFileScans(object):
         for specFile in filelist:
             self.plot_all_scans(specFile)
     
-    def _mtime_checkup_(self, specFile):
-        """
-        check the mtime of various files to see if the plot needs to be made
+    def specFileUpdated(self, specFile):
         """
-        if not os.path.exists(specFile):
-            return
-    
-        # decide here if SPEC file needs to be opened for possible replot of scan data
-        mtime_cache = Cache_File_Mtime(self.plotDir)
-        if not mtime_cache.was_file_updated(specFile):
-            return
-    
-        # Don't replot if the plot newer than the data file modification time.
-        mtime_specFile = mtime_cache.get(specFile)
-        png_directory = self.get_PngDir(specFile)
-        if os.path.exists(png_directory):
-            mtime_pngdir = get_file_mtime(png_directory)
-        else:
-            mtime_pngdir = 0
-    
-        # compare mtime of data file with mtime of PNG directory
-        if mtime_pngdir > mtime_specFile:
-            # do nothing if plot directory was last updated _after_ the specFile
-            return
+        report if specFile has been updated
         
-        return (mtime_specFile, mtime_pngdir, png_directory)
+        Return mtime cache entry (or `None` if not updated)
+        """
+        result = None
+        if os.path.exists(specFile):
+            mtime_cache = Cache_File_Mtime(self.plotDir)
+            if mtime_cache.was_file_updated(specFile):
+                result = mtime_cache.get(specFile)
+        return result
     
     def plot_all_scans(self, specFile):
         """
@@ -134,31 +130,54 @@ class PlotSpecFileScans(object):
         if not spec.is_spec_file(specFile):
             raise spec.NotASpecDataFile(specFile)
 
-        answer = self._mtime_checkup_(specFile)
-        if answer is None:
+        last_cache = Cache_File_Mtime(self.plotDir).get(specFile)
+        cache = self.specFileUpdated(specFile)
+        if cache is None:
             return
-        mtime_specFile, mtime_pngdir, png_directory = answer
 
         try:
-            logger('SPEC data file: ' + specFile)
-            logger('  updating plots in directory: ' + png_directory)
-            logger('    mtime_specFile: ' + str(mtime_specFile))
-            logger('    mtime_pngdir:   ' + str(mtime_pngdir))
+            logger("SPEC data file: %s" % specFile)
             sd = specplot.openSpecFile(specFile)
         except FileNotFoundError:
             return    # could not open file, be silent about it
         if len(sd.headers) == 0:    # no scan header found, again, silence
             return
+
+        plot_path = self.getPlotDir(specFile)
+        logger("  plot directory: %s" % plot_path)
+        if not os.path.exists(plot_path):
+            os.makedirs(plot_path)
+            logger('creating directory: ' + plot_path)
+        plot_list = [
+            k
+            for k in sorted(os.listdir(plot_path))
+            if k.endswith(PLOT_TYPE)
+            ]
+        
+        # check if any plots need to be removed (and remade)
+        if len(plot_list) > 0 and cache["size"] > 0 and cache["size"] != last_cache["size"]:
+            if cache["size"] > last_cache["size"]:
+                # Was last scan updated with more data?  Look at file.
+                with open(specFile, "r") as fp:
+                    # skip the part already considered
+                    fp.read(last_cache["size"])
+                    # look at the addition
+                    buf = fp.read()
+
+                # only delete last plot if addition not start with #S
+                if not buf.lstrip().startswith("#S "):
+                    k = plot_list.pop()
+                    os.remove(os.path.join(plot_path, k))
+            else:
+                # remake all the plots
+                for k in plot_list:     # TODO: needs unit test
+                    os.remove(os.path.join(plot_path, k))
+                plot_list = []
     
-        plotted_scans = []
         problem_scans = []
         newFileList = [] # list of all new files created
-    
-        if not os.path.exists(png_directory):
-            os.makedirs(png_directory)
-            logger('creating directory: ' + png_directory)
         
-        shutil.copy(specFile, png_directory)
+        shutil.copy(specFile, plot_path)
         
         scan_list = sd.getScanNumbers()
         if self.reversed:
@@ -166,14 +185,19 @@ class PlotSpecFileScans(object):
 
         for scan_number in scan_list:
             scan = sd.getScan(scan_number)
-            # make certain that plot files will sort lexically:  S1 --> s00001.png
-            basePlotFile = ('s%05s.png' % str(scan.scanNum)).replace(' ', '0')
-            fullPlotFile = os.path.join(png_directory, basePlotFile)
+            # make certain that plot files will sort lexically:  S1 --> s00001
+            base = 's%05s' + PLOT_TYPE
+            basePlotFile = (base % str(scan.scanNum)).replace(' ', '0')
+            fullPlotFile = os.path.join(plot_path, basePlotFile)
             altText = '#' + str(scan.scanNum) + ': ' + scan.scanCmd
             href = self.href_format(basePlotFile, altText)
+
+            if basePlotFile in plot_list:
+                newFileList.append(fullPlotFile)
+                continue
             
             #print ("specplot.py %s %s %s" % (specFile, scan.scanNum, fullPlotFile))
-            if needToMakePlot(fullPlotFile, mtime_specFile):
+            if needToMakePlot(fullPlotFile, cache["mtime"]):
                 try:
                     logger('  creating SPEC data scan image: ' + basePlotFile)
                     selector = specplot.Selector()
@@ -181,33 +205,33 @@ class PlotSpecFileScans(object):
                     plotter = image_maker()
                     plotter.plot_scan(scan, fullPlotFile)
                     newFileList.append(fullPlotFile)
-                    plotted_scans.append(href)
+                    plot_list.append(href)
                 except Exception as _exc_obj:
                     msg = "<b>%s</b>" % type(_exc_obj).__name__
                     msg += ": <tt>#S %s</tt>" % str(scan)
                     #msg += " (%s)" % specFile
                     problem_scans.append(msg)
     
-        htmlFile = os.path.join(png_directory, HTML_INDEX_FILE)
+        htmlFile = os.path.join(plot_path, HTML_INDEX_FILE)
         if len(newFileList) or not os.path.exists(htmlFile):
             logger('  creating/updating index.html file')
-            html = build_index_html(specFile, plotted_scans, problem_scans)
+            html = buildIndexHtml(specFile, plot_list, problem_scans)
             f = open(htmlFile, "w")
             f.write(html)
             f.close()
             newFileList.append(htmlFile)
             
-        # touch to update the mtime on the png_directory
-        os.utime(png_directory, None)
+        # touch to update the mtime on the plot_path
+        os.utime(plot_path, None)
     
-    def get_PngDir(self, specFile):
+    def getPlotDir(self, specFile):
         """
-        return the PNG directory based on the specFile
+        return the plot directory based on the specFile
         
         :param str specFile: name of SPEC data file (relative or absolute)
         """
         data_file_root_name = os.path.splitext(os.path.split(specFile)[1])[0]
-        date_str = get_SpecFileDate(specFile)
+        date_str = getSpecFileDate(specFile)
         if date_str is None:
             return
         return self.getBaseDir(data_file_root_name, date_str)
@@ -250,32 +274,25 @@ class Cache_File_Mtime(object):
         """read the cache from storage"""
         cache = {}
         if os.path.exists(self.cache_file):
-            for line in open(self.cache_file, 'r').readlines():
-                key = line.split('\t')[0]
-                val = float(line.strip().split('\t')[1])
-                cache[key] = val
+            with open(self.cache_file, 'r') as fp:
+                cache = json.load(fp)
         return cache
 
     def write(self):
         """write the cache to storage"""
-        f = open(self.cache_file, 'w')
-        for key, val in sorted(self.cache.items()):
-            t = str(key) + '\t' + str(val) + '\n'
-            f.write(t)
-        f.close()
+        with open(self.cache_file, 'w') as fp:
+            json.dump(self.cache, fp, indent=4)
     
-    def get(self, fname):
+    def get(self, fname, default=dict(mtime=0, size=0)):
         """
-        get the file modified time from the cache
-        
+        get the mtime cache entry for data file ``fname``
+         
         :param str fname: file name, already known to exist
         :return: time (float) cached value of when fname was 
             last modified or None if not known
         """
-        if fname in self.cache:
-            return self.cache[fname]
-        return None
-
+        return self.cache.get(fname, default)
+        
     def was_file_updated(self, fname):
         """
         compare the mtime between disk and cache
@@ -283,14 +300,21 @@ class Cache_File_Mtime(object):
         :param str fname: file name, already known to exist
         :return bool: True if file is newer than the cache (or new to the cache)
         """
-        mtime_file = get_file_mtime(fname)
-        mtime_cache = self.get(fname)
-        if mtime_cache is None or mtime_file > mtime_cache:
+        assert os.path.exists(fname)
+        updated = False
+        cache = self.get(fname)
+        
+        file_mtime = os.path.getmtime(fname)
+        file_size = os.path.getsize(fname)
+
+        if file_mtime != cache["mtime"] and file_size != cache["size"]:
+            updated = True
+
+        if updated:
             logger('SPEC data file updated: ' + fname)
-            self.cache[fname] = mtime_file
+            self.cache[fname] = dict(mtime=file_mtime, size=os.path.getsize(fname))
             self.write()
-            return True
-        return False
+        return updated
 
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
@@ -308,7 +332,7 @@ def datePath(date):
     return os.path.join(yyyy, mm)
 
 
-def get_SpecFileDate(specFile):
+def getSpecFileDate(specFile):
     """
     return the #D date of the SPEC data file or None
     
@@ -334,16 +358,6 @@ def get_SpecFileDate(specFile):
     return line[2:].strip()  # 'Thu Jun 19 12:21:55 2014'
 
 
-def get_file_mtime(filename):
-    """
-    get the file modified time from disk
-    
-    :param str fname: file name, already known to exist
-    :return: time (float) fname was last modified
-    """
-    return os.path.getmtime(filename)
-
-
 def needToMakePlot(fullPlotFile, mtime_specFile):
     """
     Determine if a plot needs to be (re)made.  Use mtime as the basis.
@@ -352,7 +366,7 @@ def needToMakePlot(fullPlotFile, mtime_specFile):
     """
     remake_plot = True
     if os.path.exists(fullPlotFile):
-        mtime_plotFile = get_file_mtime(fullPlotFile)
+        mtime_plotFile = os.path.getmtime(fullPlotFile)
         if mtime_plotFile > mtime_specFile:
             # plot was made after the data file was updated
             remake_plot = False     # don't remake the plot
@@ -371,7 +385,7 @@ def timestamp():
     return ts
 
 
-def build_index_html(specFile, plotted_scans, problem_scans):
+def buildIndexHtml(specFile, plotted_scans, problem_scans):
     """
     build index.html content
     

specplot_gallery: do not re-plot the entire spec file
From Suresh Narayanan:

_One question: I would think it would be good if it did not do the entire spec file each time. I can imagine, writing the last scan plotted to a ENV variable for example and the next time, it starts from that scan again. There is no risk of missing a scan then. Often, we have 5-10K scans in a single spec file._



diff --git a/tests/data/refresh1.txt b/tests/data/refresh1.txt
new file mode 100644
index 0000000..e40bd25
--- /dev/null
+++ b/tests/data/refresh1.txt
@@ -0,0 +1,40 @@
+#F 11_03_Vinod.dat
+#E 1288809574
+#D Wed Nov 03 13:39:34 2010
+#C Interesting samples  User = s15usaxs
+#O0     slux      sluy      sldx      sldy  dcmthfine    ion_ch       msx       msy
+
+#S 1  ascan  mr 15.6102 15.6052  30 0.3
+#D Wed Nov 03 13:42:03 2010
+#T 0.3  (seconds)
+#G0 0
+#G1 0
+#G3 0
+#G4 0
+#Q 
+#P0 -0.5396381 -0.5675 0.395862 0.7425 40.489861 0 5.894899e-07 11
+#N 15
+#L mr    ay  dy  ar_enc  pd_range  pd_counts  pd_rate  pd_curent  Epoch  seconds  I00  USAXS_PD  Monitor  I0  I0
+15.6102  -0.010 111.529 15.498553 3 99996 999960 9.99958e-08 150 0.3 38 8 100265 222 222
+15.61003  -0.010 111.529 15.498553 3 99996 999960 9.99958e-08 151 0.3 38 12 100769 293 293
+15.60987  -0.010 111.529 15.498553 3 99996 999960 9.99958e-08 151 0.3 39 18 100917 425 425
+
+#S 2  ascan  m2rp 1.9475 2.9475  40 0.2
+#D Wed Nov 03 13:42:26 2010
+#T 0.2  (seconds)
+#P0 -0.5396381 -0.5675 0.395862 0.7425 40.489861 0 5.894899e-07 11
+#N 15
+#L USAXS.m2rp    ay  dy  ar_enc  pd_range  pd_counts  pd_rate  pd_curent  Epoch  seconds  I00  USAXS_PD  Monitor  I0  I0
+1.9475  -0.010 111.529 15.498553 1 20396 203960 0.000203958 172 0.2 26 4 65024 276 276
+1.9725  -0.010 111.529 15.498553 1 20396 203960 0.000203958 173 0.2 26 4 64845 352 352
+1.9975  -0.010 111.529 15.498553 1 20396 203960 0.000203958 173 0.2 26 4 65449 478 478
+
+#S 3  ascan  ar 15.5006 15.4966  40 0.3
+#D Wed Nov 03 13:42:49 2010
+#T 0.3  (seconds)
+#P0 -0.5396381 -0.5675 0.395862 0.7425 40.489861 0 5.894899e-07 11
+#N 14
+#L ar    ay  dy  ar_enc  pd_range  pd_counts  pd_rate  pd_curent  Epoch  seconds  I00  Monitor  I0  USAXS_PD
+15.500552  -0.010 111.529 15.500552 1 21384 213840 0.000213838 196 0.3 38 100137 18254 10
+15.50045  -0.010 111.529 15.500450 1 21384 213840 0.000213838 197 0.3 38 100202 18302 12
+15.500349  -0.010 111.529 15.500349 1 21384 213840 0.000213838 199 0.3 38 100134 18335 13
diff --git a/tests/data/refresh2.txt b/tests/data/refresh2.txt
new file mode 100644
index 0000000..6ec134a
--- /dev/null
+++ b/tests/data/refresh2.txt
@@ -0,0 +1,32 @@
+15.497052  -0.010 111.529 15.497052 1 21384 213840 0.000213838 243 0.3 38 98935 18498 21
+15.496953  -0.010 111.529 15.496953 1 21384 213840 0.000213838 244 0.3 38 97949 18490 19
+15.496855  -0.010 111.529 15.496855 1 21384 213840 0.000213838 246 0.3 38 98247 18511 16
+15.496754  -0.010 111.529 15.496754 1 21384 213840 0.000213838 247 0.3 38 97598 18515 16
+15.496652  -0.010 111.529 15.496652 1 21384 213840 0.000213838 248 0.3 38 98231 18622 13
+15.496553  -0.010 111.529 15.496553 1 21384 213840 0.000213838 250 0.3 38 99174 18931 12
+
+#S 4  ascan  a2rp 2.71 3.71  40 0.2
+#D Wed Nov 03 13:43:46 2010
+#T 0.2  (seconds)
+#P0 -0.5396381 -0.5675 0.395862 0.7425 40.489861 0 5.894899e-07 11
+#N 14
+#L USAXS.a2rp    ay  dy  ar_enc  pd_range  pd_counts  pd_rate  pd_curent  Epoch  seconds  I00  Monitor  I0  USAXS_PD
+2.71  -0.010 111.529 15.498515 1 20622 206220 0.000206218 253 0.2 25 64220 12314 222
+2.735  -0.010 111.529 15.498515 1 20622 206220 0.000206218 253 0.2 25 64637 12296 271
+2.76  -0.010 111.529 15.498515 1 20622 206220 0.000206218 254 0.2 25 64970 12375 364
+2.785  -0.010 111.529 15.498515 1 20622 206220 0.000206218 254 0.2 25 64949 12350 505
+
+#S 5  uascan  ar 15.4995 15.4985 8.89886 1e-05  111.529 720 0 98 1 200 0.5
+#D Wed Nov 03 13:44:11 2010
+#T 0.5  (seconds)
+#P0 -0.5396381 -0.5675 0.395862 0.7425 40.489861 0 5.894899e-07 11
+#N 14
+#L ar  ay  dy  ar_enc  pd_range  pd_counts  pd_rate  pd_curent  Epoch  seconds  I00  Monitor  I0  USAXS_PD
+15.499516 -0.010 111.541 15.499516 3 499982 999964 9.99962e-08 279 0.5 54 165925 31174 499982
+15.49944 -0.010 111.541 15.499440 2 78085 156170 1.56168e-06 281 0.5 61 163321 30987 78085
+15.499369 -0.010 111.541 15.499369 2 248191 496382 4.9638e-06 282 0.5 58 162075 31141 248191
+15.499305 -0.010 111.541 15.499305 1 5861 11722 1.172e-05 284 0.5 63 162192 31180 5861
+15.499242 -0.010 111.541 15.499242 1 10285 20570 2.0568e-05 285 0.5 62 162747 30890 10285
+15.499185 -0.010 111.541 15.499185 1 14947 29894 2.9892e-05 286 0.5 63 164578 30977 14947
+15.499134 -0.010 111.536 15.499134 1 19463 38926 3.8924e-05 287 0.5 63 164203 31282 19463
+15.499081 -0.010 111.536 15.499081 1 23848 47696 4.7694e-05 288 0.5 62 163434 31400 23848
diff --git a/tests/data/refresh3.txt b/tests/data/refresh3.txt
new file mode 100644
index 0000000..9384fa2
--- /dev/null
+++ b/tests/data/refresh3.txt
@@ -0,0 +1,38 @@
+
+#S 6  ascan  mr 15.6102 15.6052  30 0.3
+#D Wed Nov 03 13:50:00 2010
+#T 0.3  (seconds)
+#P0 -0.5396381 -0.5675 0.395862 0.7425 40.489861 0 5.894899e-07 11
+#N 15
+#L mr    ay  dy  ar_enc  pd_range  pd_counts  pd_rate  pd_curent  Epoch  seconds  I00  USAXS_PD  Monitor  I0  I0
+15.61017  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 627 0.3 38 9 100075 243 243
+15.61  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 628 0.3 38 13 99554 325 325
+15.60984  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 628 0.3 38 19 98503 460 460
+15.60967  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 629 0.3 38 30 97645 609 609
+15.6095  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 629 0.3 38 54 98743 883 883
+15.60934  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 630 0.3 38 161 99576 1780 1780
+15.60917  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 631 0.3 38 499 99414 3649 3649
+15.609  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 631 0.3 38 1257 98135 6588 6588
+15.60884  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 632 0.3 37 2832 98817 10245 10245
+15.60867  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 633 0.3 38 7294 99262 13118 13118
+15.6085  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 633 0.3 36 139191 99913 16527 16527
+15.60834  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 634 0.3 33 299989 99074 17893 17893
+15.60817  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 635 0.3 33 299989 98955 18276 18276
+15.608  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 635 0.3 33 299989 97890 18240 18240
+15.60784  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 636 0.3 33 299989 96734 18266 18266
+15.60767  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 637 0.3 32 299989 97537 18616 18616
+15.6075  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 637 0.3 33 299989 97776 19033 19033
+15.60734  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 638 0.3 33 299989 96956 19036 19036
+15.60717  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 638 0.3 33 299988 96849 18587 18587
+15.607  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 639 0.3 33 299989 98304 17471 17471
+15.60684  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 640 0.3 36 123003 97785 14814 14814
+15.60667  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 640 0.3 37 11060 97288 11861 11861
+15.6065  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 641 0.3 38 2217 97366 8131 8131
+15.60634  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 642 0.3 38 637 99347 4269 4269
+15.60617  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 642 0.3 38 254 98910 2632 2632
+15.606  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 643 0.3 38 132 99479 1927 1927
+15.60584  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 644 0.3 38 79 98081 1406 1406
+15.60567  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 644 0.3 38 58 99015 1075 1075
+15.6055  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 645 0.3 38 32 98985 695 695
+15.60534  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 645 0.3 38 17 100338 374 374
+15.60517  -0.010 111.529 15.498519 3 99997 999970 9.99968e-08 646 0.3 38 10 99683 245 245
diff --git a/tests/test_spec.py b/tests/test_spec.py
index d04f317..9f71d6d 100644
--- a/tests/test_spec.py
+++ b/tests/test_spec.py
@@ -28,6 +28,12 @@ sys.path.insert(0, _test_path)
 from spec2nexus import spec, utils
 
 
+# interval between file update and mtime reading
+# at least a clock tick (1/60 s)
+# or at least 1 second if not using float time for os.path.getmtime
+SHORT_WAIT = 0.1
+
+
 class Test(unittest.TestCase):
     
     def abs_data_fname(self, fname):
@@ -367,37 +373,57 @@ class TestFileUpdate(unittest.TestCase):
         self.data_file = tempfile.NamedTemporaryFile(
             suffix='.dat', delete=False)
         self.data_file.close()
+        file1 = os.path.join(_test_path, "tests", "data", "refresh1.txt")
+        shutil.copy(file1, self.data_file.name)
+
+    def addMoreScans(self):
+        file2 = os.path.join(_test_path, "tests", "data", "refresh2.txt")
+        with open(file2, "r") as fp:
+            text = fp.read()
+        with open(self.data_file.name, "a") as fp:
+            fp.write(text)
 
     def tearDown(self):
         os.remove(self.data_file.name)
     
     def test_update_available(self):
-        # test the mtime function first
-        # and setup the modifiable SPEC data file
-        self.assertTrue(os.path.exists(self.data_file.name))
-        mt0 = os.path.getmtime(self.data_file.name)
-        time.sleep(0.02)        # at least a clock tick (1/60 s)
-        shutil.copy(
-            os.path.join(_test_path, "tests", "data", "issue82_data.txt"),
-            self.data_file.name)
-        mt1 = os.path.getmtime(self.data_file.name)
-        self.assertGreater(mt1, mt0)
-        
         # test the ``update_available`` property
         sdf = spec.SpecDataFile(self.data_file.name)
+        self.assertGreater(sdf.mtime, 0)
         self.assertFalse(sdf.update_available)
-        self.assertEqual(sdf.num_lines, 164)
+        self.assertEqual(sdf.filesize, 1837)       # OS dependent?
         self.assertEqual(sdf.last_scan, sdf.getLastScanNumber())
-        self.assertEqual(sdf.last_scan, '17')
+        self.assertEqual(sdf.last_scan, '3')
 
-        # update the file with a trivial edit
-        with open(self.data_file.name, "a") as fp:
-            fp.write("\n#C comment\n")
-        time.sleep(0.02)        # at least a clock tick (1/60 s)
+        # update the file with more data
+        self.addMoreScans()
+        time.sleep(SHORT_WAIT)
 
-        mt2 = os.path.getmtime(self.data_file.name)
-        self.assertGreater(mt2, mt1)
         self.assertTrue(sdf.update_available)
+    
+    def test_refresh(self):
+        sdf = spec.SpecDataFile(self.data_file.name)
+        self.assertNotEqual(sdf.last_scan, None)
+        self.assertEqual(len(sdf.getScanNumbers()), 3)
+        self.assertEqual(sdf.filesize, 1837)       # OS dependent?
+
+        # update the file with more data
+        self.addMoreScans()
+        time.sleep(SHORT_WAIT)
+
+        scan_number = sdf.refresh()
+        self.assertGreater(sdf.filesize, 1837)
+        self.assertEqual(len(sdf.getScanNumbers()), 5)
+        self.assertNotEqual(scan_number, None)
+        self.assertNotEqual(sdf.last_scan, None)
+        self.assertNotEqual(scan_number, sdf.last_scan)
+        self.assertNotEqual(scan_number, sdf.getLastScanNumber())
+        self.assertEqual(sdf.last_scan, sdf.getLastScanNumber())
+
+        time.sleep(SHORT_WAIT)
+        scan_number = sdf.refresh()
+        self.assertEqual(scan_number, None)
+        self.assertEqual(len(sdf.getScanNumbers()), 5)
 
 
 def suite(*args, **kw):
diff --git a/tests/test_specplot.py b/tests/test_specplot.py
index 3b43cd7..29de5ba 100644
--- a/tests/test_specplot.py
+++ b/tests/test_specplot.py
@@ -12,10 +12,12 @@ unit tests for the specplot module
 # The full license is in the file LICENSE.txt, distributed with this software.
 #-----------------------------------------------------------------------------
 
-import unittest
-import os, sys
+import os
 import shutil
+import sys
 import tempfile
+import time
+import unittest
 
 _test_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
 _path = os.path.abspath(os.path.join(_test_path, 'src'))
@@ -218,10 +220,66 @@ class Issue_66_plotting_problems(unittest.TestCase):
         self.assertTrue(os.path.exists(self.plotFile))
 
 
+class TestFileUpdate(unittest.TestCase):
+
+    def setUp(self):
+        self.data_file = tempfile.NamedTemporaryFile(
+            suffix='.dat', delete=False)
+        self.data_file.close()
+        file1 = os.path.join(_test_path, "tests", "data", "refresh1.txt")
+        shutil.copy(file1, self.data_file.name)
+        
+        self.tempdir = tempfile.mkdtemp()
+
+    def addMoreScans(self):
+        file2 = os.path.join(_test_path, "tests", "data", "refresh2.txt")
+        with open(file2, "r") as fp:
+            text = fp.read()
+        with open(self.data_file.name, "a") as fp:
+            fp.write(text)
+
+    def tearDown(self):
+        os.remove(self.data_file.name)
+        shutil.rmtree(self.tempdir, ignore_errors=True)
+    
+    def test_refresh(self):
+        plot = os.path.join(self.tempdir, 'plot.svg')
+        plot2 = os.path.join(self.tempdir, 'plot2.svg')
+
+        sdf = specplot.openSpecFile(self.data_file.name)
+        scan = sdf.getScan(3)
+        plotter = specplot.LinePlotter()
+
+        # plot the first data
+        plotter.plot_scan(scan, plot)
+        self.assertTrue(os.path.exists(plot))
+        plotsize = os.path.getsize(plot)
+        mtime = os.path.getmtime(plot)
+        self.assertGreater(plotsize, 0)
+        self.assertGreater(mtime, 0)
+        
+        for iter in range(2):
+            scan_number = sdf.refresh()
+            if scan_number is None:
+                scan = sdf.getScan(3)
+                self.assertTrue(scan.__interpreted__)
+                # update the file with more data
+                self.addMoreScans()
+                time.sleep(0.1)
+            else:
+                scan = sdf.getScan(3)
+                self.assertFalse(scan.__interpreted__)
+                plotter.plot_scan(scan, plot2)
+                self.assertTrue(os.path.exists(plot2))
+                self.assertGreater(os.path.getmtime(plot2), mtime)
+                self.assertNotEqual(os.path.getsize(plot2), plotsize)
+
+
 def suite(*args, **kw):
     test_suite = unittest.TestSuite()
     test_list = [
         Issue_66_plotting_problems,
+        TestFileUpdate,
         ]
     for test_case in test_list:
         test_suite.addTest(unittest.makeSuite(test_case))
diff --git a/tests/test_specplot_gallery.py b/tests/test_specplot_gallery.py
index deb5617..8f1f807 100644
--- a/tests/test_specplot_gallery.py
+++ b/tests/test_specplot_gallery.py
@@ -1,5 +1,5 @@
 '''
-unit tests for the specplot module
+unit tests for the specplot_gallery module
 '''
 
 #-----------------------------------------------------------------------------
@@ -13,9 +13,11 @@ unit tests for the specplot module
 #-----------------------------------------------------------------------------
 
 import logging
-import os, sys
+import os
 import shutil
+import sys
 import tempfile
+import time
 import unittest
 
 
@@ -60,8 +62,8 @@ class SpecPlotGallery(unittest.TestCase):
         specplot_gallery.main()
         # this is HDF5 file, not SPEC, so not much content
         children = os.listdir(self.tempdir)
-        self.assertEqual(len(children), 1)
-        self.assertEqual(children[0], 'specplot_files_processing.log')
+        self.assertEqual(len(children), 0)
+        # self.assertEqual(children[0], 'specplot_files_processing.log')
     
     def test_command_line_spec_data_file_33bm_spec(self):
         sys.argv.append('-d')
@@ -69,7 +71,7 @@ class SpecPlotGallery(unittest.TestCase):
         sys.argv.append(self.tempdir)
         sys.argv.append(self.abs_data_fname('33bm_spec.dat'))
         specplot_gallery.main()
-        self.assertTrue(os.path.exists(os.path.join(self.tempdir, 'mtime_cache.txt')))
+        self.assertTrue(os.path.exists(os.path.join(self.tempdir, specplot_gallery.MTIME_CACHE_FILE)))
         # TODO: test contents of mtime_cache.txt?
 
         plotDir = os.path.join(self.tempdir, '2010', '06', '33bm_spec')
@@ -85,7 +87,7 @@ class SpecPlotGallery(unittest.TestCase):
         sys.argv.append(self.abs_data_fname('user6idd.dat'))
         specplot_gallery.main()
  
-        self.assertTrue(os.path.exists(os.path.join(self.tempdir, 'mtime_cache.txt')))
+        self.assertTrue(os.path.exists(os.path.join(self.tempdir, specplot_gallery.MTIME_CACHE_FILE)))
  
         # S1 aborted, S2 all X,Y are 0,0
         plotDir = os.path.join(self.tempdir, '2013', '10', 'user6idd')
@@ -101,16 +103,16 @@ class SpecPlotGallery(unittest.TestCase):
         sys.argv.append(self.abs_data_fname('03_06_JanTest.dat'))
         specplot_gallery.main()
  
-        self.assertTrue(os.path.exists(os.path.join(self.tempdir, 'mtime_cache.txt')))
+        self.assertTrue(os.path.exists(os.path.join(self.tempdir, specplot_gallery.MTIME_CACHE_FILE)))
  
         # S1 aborted, S2 all X,Y are 0,0
         plotDir = os.path.join(self.tempdir, '2014', '03', '03_06_JanTest')
         self.assertTrue(os.path.exists(plotDir))
         self.assertTrue(os.path.exists(os.path.join(plotDir, '03_06_JanTest.dat')))
         self.assertTrue(os.path.exists(os.path.join(plotDir, 'index.html')))
-        self.assertTrue(os.path.exists(os.path.join(plotDir, 's00001.png')))
+        self.assertTrue(os.path.exists(os.path.join(plotDir, 's00001' + specplot_gallery.PLOT_TYPE)))
         # TODO: #69: look for handling of scan 1
-        self.assertFalse(os.path.exists(os.path.join(plotDir, 's1.png')))
+        self.assertFalse(os.path.exists(os.path.join(plotDir, 's1' + specplot_gallery.PLOT_TYPE)))
         # TODO: look for that scan in index.html?
      
     def test_command_line_spec_data_file_02_03_setup(self):
@@ -120,7 +122,7 @@ class SpecPlotGallery(unittest.TestCase):
         sys.argv.append(self.abs_data_fname('02_03_setup.dat'))
         specplot_gallery.main()
  
-        self.assertTrue(os.path.exists(os.path.join(self.tempdir, 'mtime_cache.txt')))
+        self.assertTrue(os.path.exists(os.path.join(self.tempdir, specplot_gallery.MTIME_CACHE_FILE)))
  
         plotDir = os.path.join(self.tempdir, '2016', '02', '02_03_setup')
         self.assertTrue(os.path.exists(plotDir))
@@ -136,13 +138,12 @@ class SpecPlotGallery(unittest.TestCase):
             sys.argv.append(self.abs_data_fname(item))
         specplot_gallery.main()
  
-        self.assertTrue(os.path.exists(os.path.join(self.tempdir, 'mtime_cache.txt')))
+        self.assertTrue(os.path.exists(os.path.join(self.tempdir, specplot_gallery.MTIME_CACHE_FILE)))
  
         plotDir = os.path.join(self.tempdir, '2010', '11', 'APS_spec_data')
         self.assertTrue(os.path.exists(plotDir))
         self.assertTrue(os.path.exists(os.path.join(plotDir, 'APS_spec_data.dat')))
         self.assertTrue(os.path.exists(os.path.join(plotDir, 'index.html')))
-        # TODO: test the order of plots in the index.html
  
         plotDir = os.path.join(self.tempdir, '2013', '10', 'user6idd')
         self.assertTrue(os.path.exists(plotDir))
@@ -166,7 +167,6 @@ class SpecPlotGallery(unittest.TestCase):
         self.assertTrue(os.path.exists(plotDir))
         self.assertTrue(os.path.exists(os.path.join(plotDir, 'APS_spec_data.dat')))
         self.assertTrue(os.path.exists(os.path.join(plotDir, 'index.html')))
-        # TODO: test the order of plots in the index.html, reversed
 
     def test_command_line_specified_directory_not_found_issue_98(self):
         sys.argv.append('-d')
@@ -185,9 +185,158 @@ class SpecPlotGallery(unittest.TestCase):
         self.assertRaises(specplot_gallery.PathIsNotDirectoryError, specplot_gallery.main)
 
 
+class TestFileRefresh(unittest.TestCase):
+
+    def setUp(self):
+        self.tempdir = tempfile.mkdtemp()
+        self.data_dir = os.path.join(self.tempdir, "data")
+        self.data_file = os.path.join(self.data_dir, "specdata.txt")
+        self.gallery = os.path.join(self.tempdir, "gallery")
+
+        os.mkdir(self.data_dir)
+        os.mkdir(self.gallery)
+        src = os.path.join(_test_path, "tests", "data", "refresh1.txt")
+        shutil.copy(src, self.data_file)
+        
+        logging.disable(logging.CRITICAL)
+
+    def addMoreScans(self, append_scan=True):
+        path = os.path.join(_test_path, "tests", "data")
+        if append_scan:
+            file2 = os.path.join(path, "refresh2.txt")
+        else:
+            file2 = os.path.join(path, "refresh3.txt")
+        with open(file2, "r") as fp:
+            text = fp.read()
+        with open(self.data_file, "a") as fp:
+            fp.write(text)
+
+    def tearDown(self):
+        shutil.rmtree(self.tempdir, ignore_errors=True)
+        logging.disable(logging.NOTSET)
+    
+    def test_refresh(self):
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+        self.assertTrue(
+            os.path.exists(
+                os.path.join(
+                    self.gallery, 
+                    specplot_gallery.MTIME_CACHE_FILE)))
+
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+        plotdir = os.path.join(self.gallery, "2010", "11", "specdata")
+        children = [
+            k
+            for k in sorted(os.listdir(plotdir))
+            if k.endswith(specplot_gallery.PLOT_TYPE)
+            ]
+        self.assertEqual(len(children), 3)
+        mtimes = {
+            k: os.path.getmtime(os.path.join(plotdir, k))
+            for k in children
+            }
+        self.assertEqual(len(mtimes), 3)
+        
+        # update the file with more data
+        self.addMoreScans()
+        time.sleep(0.1)
+
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+        k = children[-1]
+        self.assertNotEqual(
+            os.path.getmtime(os.path.join(plotdir, k)), 
+            mtimes[k], 
+            k)
+        for k in children[:-1]:
+            # should pass all but the latest (#S 3)
+            self.assertEqual(
+                os.path.getmtime(os.path.join(plotdir, k)), 
+                mtimes[k], 
+                k)
+        children = [
+            k
+            for k in sorted(os.listdir(plotdir))
+            if k.endswith(specplot_gallery.PLOT_TYPE)
+            ]
+        mtimes = {
+            k: os.path.getmtime(os.path.join(plotdir, k))
+            for k in children
+            }
+        self.assertEqual(len(children), 5)
+        
+        # update the file with another scan
+        self.addMoreScans(False)
+        time.sleep(0.1)
+
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+        children = [
+            k
+            for k in sorted(os.listdir(plotdir))
+            if k.endswith(specplot_gallery.PLOT_TYPE)
+            ]
+        self.assertEqual(len(children), 6)
+        for k in children[:-1]:
+            # should pass all but newest scan
+            self.assertEqual(
+                os.path.getmtime(os.path.join(plotdir, k)), 
+                mtimes[k], 
+                k)
+
+        # restart file with first set of scans, should trigger replot all
+        t0 = time.time()
+        time.sleep(0.1)
+        src = os.path.join(_test_path, "tests", "data", "refresh1.txt")
+        shutil.copy(src, self.data_file)
+
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+        children = [
+            k
+            for k in sorted(os.listdir(plotdir))
+            if k.endswith(specplot_gallery.PLOT_TYPE)
+            ]
+        self.assertEqual(len(children), 3)
+        for k in children:
+            # should pass all
+            self.assertGreater(
+                os.path.getmtime(os.path.join(plotdir, k)), 
+                t0, 
+                k)
+
+        self.addMoreScans()
+        time.sleep(0.1)
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+
+        # restart file again, use reversed chronological order
+        t0 = time.time()
+        time.sleep(0.1)
+        src = os.path.join(_test_path, "tests", "data", "refresh1.txt")
+        shutil.copy(src, self.data_file)
+
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery,
+            reverse_chronological=True)
+        self.assertEqual(len(children), 3)
+        self.addMoreScans()
+        time.sleep(0.1)
+        specplot_gallery.PlotSpecFileScans(
+            [self.data_file], self.gallery)
+        children = [
+            k
+            for k in sorted(os.listdir(plotdir))
+            if k.endswith(specplot_gallery.PLOT_TYPE)
+            ]
+
+
 def suite(*args, **kw):
     test_suite = unittest.TestSuite()
     test_list = [
+        TestFileRefresh,
         SpecPlotGallery,
         ]
     for test_case in test_list:

pip install -e .[dev]
parse_log_pytest
requirements.txt
versioneer coverage coveralls pytest
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
cached-property==1.5.2
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
coveralls==3.3.1
cycler==0.11.0
docopt==0.6.2
h5py==3.1.0
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
kiwisolver==1.3.1
lxml==5.3.1
matplotlib==3.3.4
numpy==1.19.5
packaging==21.3
Pillow==8.4.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.9.0.post0
requests==2.27.1
six==1.17.0
-e git+https://github.com/prjemian/spec2nexus.git@3833681ae4da291f856eb2546cc847b93d343459#egg=spec2nexus
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
versioneer==0.22
zipp==3.6.0

name: spec2nexus
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cached-property==1.5.2
      - charset-normalizer==2.0.12
      - coverage==6.2
      - coveralls==3.3.1
      - cycler==0.11.0
      - docopt==0.6.2
      - h5py==3.1.0
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - kiwisolver==1.3.1
      - lxml==5.3.1
      - matplotlib==3.3.4
      - numpy==1.19.5
      - packaging==21.3
      - pillow==8.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - requests==2.27.1
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - versioneer==0.22
      - zipp==3.6.0
prefix: /opt/conda/envs/spec2nexus

tests/test_spec.py::TestFileUpdate::test_refresh tests/test_spec.py::TestFileUpdate::test_update_available tests/test_specplot.py::TestFileUpdate::test_refresh tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_spec_data_file_03_06_JanTest tests/test_specplot_gallery.py::TestFileRefresh::test_refresh
tests/test_spec.py::Test::test_33bm_spec
tests/test_spec.py::Test::test_33id_spec tests/test_spec.py::Test::test_APS_spec_data tests/test_spec.py::Test::test_CdSe tests/test_spec.py::Test::test_YSZ011_ALDITO_Fe2O3_planar_fired_1 tests/test_spec.py::Test::test_custom_exceptions tests/test_spec.py::Test::test_extra_control_line_content__issue109 tests/test_spec.py::Test::test_file_initial_exceptions tests/test_spec.py::Test::test_isSpecFile tests/test_spec.py::Test::test_isSpecFileThis tests/test_spec.py::Test::test_lmn40 tests/test_spec.py::Test::test_str tests/test_spec.py::Test::test_strip_first_word tests/test_specplot.py::Issue_66_plotting_problems::test_40x35_grid_shown_properly_lmn40_spe tests/test_specplot.py::Issue_66_plotting_problems::test_command_line tests/test_specplot.py::Issue_66_plotting_problems::test_command_line_33bm_spec_issue72_hklmesh_plot tests/test_specplot.py::Issue_66_plotting_problems::test_command_line_33bm_spec_issue80_hklscan_plot tests/test_specplot.py::Issue_66_plotting_problems::test_command_line_33id_spec_issue72_mesh_plot tests/test_specplot.py::Issue_66_plotting_problems::test_one_line_mesh_scan_as_1D_plot_issue82 tests/test_specplot.py::Issue_66_plotting_problems::test_one_line_mesh_scan_type_error_33id_29 tests/test_specplot.py::Issue_66_plotting_problems::test_scan_aborted_after_0_points tests/test_specplot.py::Issue_66_plotting_problems::test_y_values_all_zero_lin_lin tests/test_specplot.py::Issue_66_plotting_problems::test_y_values_all_zero_log_lin tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_NeXus_writer_1_3 tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_spec_data_file_02_03_setup tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_spec_data_file_33bm_spec tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_spec_data_file_list tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_spec_data_file_list_reversed_chronological_issue_79 tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_spec_data_file_user6idd tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_specified_directory_fails_isdir_issue_98 tests/test_specplot_gallery.py::SpecPlotGallery::test_command_line_specified_directory_not_found_issue_98
src/spec2nexus/specplot_gallery.py src/spec2nexus/plugins/spec_common.py src/spec2nexus/spec.py
src/spec2nexus/specplot_gallery.py src/spec2nexus/plugins/spec_common.py src/spec2nexus/spec.py