• R/O
  • SSH

execsql: Commit

Default repository for execsql.py


Commit MetaInfo

Révisionf98ba2c7f7e870a6b70f1c49cb8279d0afc0fbcc (tree)
l'heure2022-01-10 01:06:30
Auteurrdnielsen
Commiterrdnielsen

Message de Log

Added the CONTAINS, ENDS_WITH, and STARTS_WITH conditional tests.

Change Summary

Modification

diff -r c13ded0ca50b -r f98ba2c7f7e8 CHANGELOG.rst
--- a/CHANGELOG.rst Sat Jan 08 06:37:54 2022 -0800
+++ b/CHANGELOG.rst Sun Jan 09 08:06:30 2022 -0800
@@ -1,6 +1,7 @@
11 ========== ========== =================================================================================
22 Version Date Features
33 ========== ========== =================================================================================
4+1.97.0 2022-01-08 Added the CONTAINS, ENDS_WITH, and STARTS_WITH conditional tests. Modified the 'textarea' control in an ENTRY_FORM to allow newlines to be inserted, and to strip trailing newlines. Modified the SQL statement evaluator to ignore multiple terminating semicolons.
45 1.96.0 2022-01-03 Reading of .xlsx files now uses the openpyxl library--a new requirement.
56 1.95.0 2021-12-03 The SYSTEM_CMD metacommand now logs the command to execsql.log.
67 1.94.0 2021-10-19 Modified the INCLUDE and IMPORT metacommands to recognize leading tildes on the filename, and added the $PATHSEP system variable.
diff -r c13ded0ca50b -r f98ba2c7f7e8 doc/source/conf.py
--- a/doc/source/conf.py Sat Jan 08 06:37:54 2022 -0800
+++ b/doc/source/conf.py Sun Jan 09 08:06:30 2022 -0800
@@ -58,9 +58,9 @@
5858 # built documents.
5959 #
6060 # The short X.Y version.
61-version = u'1.96'
61+version = u'1.97'
6262 # The full version, including alpha/beta/rc tags.
63-release = u'1.96'
63+release = u'1.97'
6464
6565 # A string of reStructuredText that will be included at the beginning of
6666 # every source file that is read.
diff -r c13ded0ca50b -r f98ba2c7f7e8 doc/source/examples.rst
--- a/doc/source/examples.rst Sat Jan 08 06:37:54 2022 -0800
+++ b/doc/source/examples.rst Sun Jan 09 08:06:30 2022 -0800
@@ -2068,3 +2068,188 @@
20682068 of the attributes in the base table are different in the staging table.
20692069
20702070
2071+
2072+.. index::
2073+ single: Interactive querying
2074+
2075+.. _example32:
2076+
2077+**Example 32:** Interactive Querying
2078+--------------------------------------------------------------------------------
2079+
2080+Although execsql is designed to support execution of SQL scripts, its features can also
2081+be used to create simple interfaces to allow interactive querying. Two different
2082+approaches are illustrated in the following code snippets.
2083+
2084+a) Using PROMPT ENTER_SUB
2085+....................................
2086+
2087+This approach repeatedly displays a single-line entry area in which a SQL statement
2088+can be entered, then displays a view that results from executing that statement,
2089+with a prompt for an additional statement.
2090+
2091+::
2092+
2093+ -- !x! sub msg !!$current_database!!
2094+ -- !x! sub_append msg Enter the SQL to execute.
2095+
2096+ -- !x! cancel_halt off
2097+ -- !x! prompt enter_sub sql_stmt message "!!msg!!"
2098+ -- !x! loop while (not dialog_canceled())
2099+ drop view if exists sql_result cascade;
2100+ create temporary view sql_result as !!sql_stmt!!;
2101+ -- !x! sub msg !!$current_database!!
2102+ -- !x! sub_append msg Previous SQL: !!sql_stmt!!
2103+ -- !x! sub_append msg Enter the SQL to execute.
2104+ -- !x! prompt enter_sub sql_stmt message "!!msg!!" display sql_result
2105+ -- !x! end loop
2106+
2107+b) Using PROMPT ENTRY_FORM
2108+....................................
2109+
2110+This approach is similar to the first one, but uses the :ref:`PROMPT ENTRY_FORM <prompt_entry>`
2111+metacommand to present a multi-line entry area for the SQL statement, and also to prompt
2112+for a description of the query. In addition, each SQL statement and its description
2113+are saved to a SQL script file (*sql_statements.sql*), and the output of each statement
2114+is not only displayed on the screen but is also saved to an ODS workbook (*sql_output.ods*).
2115+
2116+This example requires the use of execsql version 1.97 or later.
2117+
2118+::
2119+
2120+ create temporary table get_sql (
2121+ sub_var text,
2122+ prompt text,
2123+ required boolean default True,
2124+ initial_value text,
2125+ width integer,
2126+ entry_type text,
2127+ sequence integer
2128+ );
2129+
2130+ insert into get_sql
2131+ (sub_var, prompt, entry_type, width, sequence)
2132+ values
2133+ ('description', 'Query description', null, 50, 1),
2134+ ('sql_stmt', 'Query SQL', 'textarea', 50, 2);
2135+
2136+
2137+ -- !x! sub msg !!$current_database!!
2138+
2139+ -- !x! rm_file sql_statements.sql
2140+ -- !x! rm_file sql_output.ods
2141+
2142+ -- !x! cancel_halt off
2143+ -- !x! prompt entry_form get_sql message "!!msg!!"
2144+ -- !x! loop while (not dialog_canceled())
2145+ -- !x! sub stmt_no !!$counter_1!!
2146+ -- !x! sub query_name query_!!stmt_no!!
2147+ drop view if exists !!query_name!! cascade;
2148+ create temporary view !!query_name!! as !!sql_stmt!!;
2149+ -- !x! write "-- Query !!stmt_no!!" to sql_statements.sql
2150+ -- !x! write "-- !!description!!" to sql_statements.sql
2151+ -- !x! write `!!sql_stmt!!` to sql_statements.sql
2152+ -- !x! write "" to sql_statements.sql
2153+ -- !x! export !!query_name!! append to sql_output.ods as ods description "!!description!!"
2154+ update get_sql
2155+ set initial_value = '!!sql_stmt!!'
2156+ where sub_var = 'sql_stmt';
2157+ -- !x! prompt entry_form get_sql message "!!msg!!" display !!query_name!!
2158+ -- !x! end loop
2159+
2160+
2161+
2162+
2163+.. index::
2164+ single: Output Run Numbering
2165+
2166+.. _example33:
2167+
2168+**Example 33:** Sequential Numbering of Script Output
2169+--------------------------------------------------------------------------------
2170+
2171+Unique numbering of script output is useful when a script is to be run repeatedly,
2172+when it will produce different output each time, and when all of the output files
2173+are to be retained.
2174+
2175+This example illustrates one way in which this can be done. The features of this
2176+process are:
2177+
2178+ * All output is created in subdirectories of a 'parent' output directory. In
2179+ this example, the parent output directory is named "DB_output", which is
2180+ created under the current directory.
2181+ * Each output-specific subdirectory has a name of the form "Run_nnn_yyyymmdd"
2182+ where "nnn" is the run number, and "yyyymmdd" is the date of the run.
2183+ * The user may be prompted for a run description, based on a setting in the
2184+ script. If a run description is provided it is saved as a text file in
2185+ the output directory. All run numbers and run descriptions are also saved
2186+ in a CSV file in the current directory named "DB_run_descriptions.csv".
2187+ * A substitution variable named "output_dir" is created that should be
2188+ used in file/path names for output of the :ref:`EXPORT <export>` and
2189+ :ref:`WRITE <write>` metacommands.
2190+
2191+ ::
2192+
2193+ -- !x! config make_export_dirs Yes
2194+
2195+ -- The "output_dir" variable will be set to a run-specific directory name
2196+ -- that is dynamically created and is different for each run of this
2197+ -- script. The run-specific output directories will be created underneath
2198+ -- a parent directory. The path to that parent directory is specified
2199+ -- here. By default, the parent directory is named "DB_output" and is
2200+ -- under the current directory.
2201+ -- !x! sub output_parent DB_output
2202+
2203+ -- Create a unique number for every run, with corresponding output
2204+ -- directories and optionally a narrative descriptions.
2205+ -- !x! sub do_run_numbering True
2206+ -- Prompt for a run description?
2207+ -- !x! sub get_run_description True
2208+
2209+ -- Set up the run number and optionally get a run description and save it.
2210+ -- A run number is always assigned, but it is used for the output
2211+ -- directory only if the 'do_run_numbering' option is True.
2212+ -- !x! if(file_exists(DB_run.conf))
2213+ -- !x! sub_ini file DB_run.conf section run
2214+ -- !x! endif
2215+ -- !x! if(not sub_defined(run_no))
2216+ -- !x! sub run_no 0
2217+ -- !x! endif
2218+ -- !x! sub_add run_no 1
2219+ -- !x! rm_file DB_run.conf
2220+ -- !x! write "# Automatically-generated database run number setting. Do not edit." to DB_run.conf
2221+ -- !x! write "[run]" to DB_run.conf
2222+ -- !x! write "run_no=!!run_no!!" to DB_run.conf
2223+ -- !x! sub run_tag !!run_no!!
2224+ -- !x! if(not is_gt(!!run_no!!, 9))
2225+ -- !x! sub run_tag 0!!run_tag!!
2226+ -- !x! endif
2227+ -- !x! if(not is_gt(!!run_no!!, 99))
2228+ -- !x! sub run_tag 0!!run_tag!!
2229+ -- !x! endif
2230+ -- !x! if(!!do_run_numbering!!)
2231+ -- !x! sub output_dir !!output_parent!!!!$pathsep!!Run_!!run_tag!!_!!$date_tag!!
2232+ -- !x! sub logfile logfiles!!$pathsep!!Run_!!run_tag!!_logfile.txt
2233+ -- !x! if(!!get_run_description!!)
2234+ -- !x! prompt enter_sub run_description message "Please enter a description for this run (!!run_tag!!). Do not use apostrophes."
2235+ -- !x! if(not sub_empty(run_description))
2236+ -- !x! write "!!run_description!!" to !!output_dir!!!!$pathsep!!run_description.txt
2237+ -- !x! export query <<select !!run_no!! as "Run_no", '!!run_description!!' as "Description";>> append to DB_run_descriptions.csv as csv
2238+ -- !x! endif
2239+ -- !x! endif
2240+ -- !x! else
2241+ -- Use just a date-tagged directory name for "output_dir".
2242+ -- !x! sub output_dir !!output_parent!!!!$pathsep!!!!$date_tag!!
2243+ -- !x! endif
2244+
2245+The "output_dir" variable that is created by this script can be used in the following way:
2246+
2247+::
2248+
2249+ -- !x! EXPORT summary_query TO !!output_dir!!!!$pathsep!!output_file.csv AS CSV
2250+
2251+This example is also incorporated into the SQL script template file *script_template.sql*
2252+available from
2253+`https://osdn.net/project/execsql/releases <https://osdn.net/project/execsql/releases>`_.
2254+The script template also uses the run number for custom logfiles.
2255+
diff -r c13ded0ca50b -r f98ba2c7f7e8 doc/source/metacommands.rst
--- a/doc/source/metacommands.rst Sat Jan 08 06:37:54 2022 -0800
+++ b/doc/source/metacommands.rst Sun Jan 09 08:06:30 2022 -0800
@@ -2382,6 +2382,24 @@
23822382
23832383
23842384
2385+.. index:: ! CONTAINS test
2386+
2387+.. _contains:
2388+
2389+*CONTAINS* test
2390+...................................
2391+
2392+::
2393+
2394+ CONTAINS("<string1>", "<string2>" [, I])
2395+
2396+Evaluates whether *string2* is contained within *string1*. The
2397+comparison is case-sensitive unless the optional argument "I" is used.
2398+The strings may be unquoted if they do not contain any spaces, or may
2399+be quoted with double quotes ("), apostrophes (') or backticks (`).
2400+
2401+
2402+
23852403 .. index:: ! DATABASE_NAME test
23862404 single: CURRENT_DATABASE system variable
23872405
@@ -2449,6 +2467,25 @@
24492467 Evaluates whether there is an existing directory with the given name.
24502468
24512469
2470+
2471+.. index:: ! ENDS_WITH test
2472+
2473+.. _ends_with:
2474+
2475+*ENDS_WITH* test
2476+...................................
2477+
2478+::
2479+
2480+ ENDS_WITH("<string1>", "<string2>" [, I])
2481+
2482+Evaluates whether *string1* ends with *string2*. The comparison is
2483+case-sensitive unless the optional argument "I" is used. The strings
2484+may be unquoted if they do not contain any spaces, or may be quoted
2485+with double quotes ("), apostrophes (') or backticks (`).
2486+
2487+
2488+
24522489 .. index:: ! EQUAL test
24532490 single: Unicode
24542491 single: IDENTICAL test
@@ -2750,6 +2787,24 @@
27502787 of :ref:`ERROR_HALT <error_halt>` or the use of the IF(SQL_ERROR()) test.
27512788
27522789
2790+.. index:: ! STARTS_WITH test
2791+
2792+.. _starts_with:
2793+
2794+*STARTS_WITH* test
2795+...................................
2796+
2797+::
2798+
2799+ STARTS_WITH("<string1>", "<string2>" [, I])
2800+
2801+Evaluates whether *string1* starts with *string2*. The comparison is
2802+case-sensitive unless the optional argument "I" is used. The strings
2803+may be unquoted if they do not contain any spaces, or may be quoted
2804+with double quotes ("), apostrophes (') or backticks (`).
2805+
2806+
2807+
27532808 .. index:: ! SUB_DEFINED test
27542809 single: Substitution variables
27552810
@@ -4028,8 +4083,8 @@
40284083 variable is always defined and assigned a value of "0" or "1".
40294084
40304085 The <Enter> key will carry out the action of the "Continue" button
4031-unless there is a multi-line text area on the form. The <Control-Enter>
4032-key chord will carry out the action of the "Continue" button in all
4086+unless there is a multi-line text area on the form. The <F5>
4087+key will carry out the action of the "Continue" button in all
40334088 cases. The <Esc> key will carry out the action of the "Cancel"
40344089 button.
40354090
diff -r c13ded0ca50b -r f98ba2c7f7e8 execsql/execsql.py
--- a/execsql/execsql.py Sat Jan 08 06:37:54 2022 -0800
+++ b/execsql/execsql.py Sun Jan 09 08:06:30 2022 -0800
@@ -27,12 +27,12 @@
2727 #
2828 # ===============================================================================
2929
30-__version__ = "1.96.1"
30+__version__ = "1.97.0"
3131 __vdate = "2022-01-08"
3232
3333 primary_vno = 1
34-secondary_vno = 96
35-tertiary_vno = 1
34+secondary_vno = 97
35+tertiary_vno = 0
3636
3737 import os
3838 import os.path
@@ -5930,7 +5930,7 @@
59305930 # are not implemented because the Stmt class would be trivial: just
59315931 # an assignment in the init method.
59325932 def __init__(self, sql_statement):
5933- self.statement = sql_statement
5933+ self.statement = re.sub(r'\s*;(\s*;\s*)+$', ';', sql_statement)
59345934 def __repr__(self):
59355935 return u"SqlStmt(%s)" % self.statement
59365936 def run(self, localvars=None, commit=True):
@@ -7861,7 +7861,7 @@
78617861 has_textarea = True
78627862 self.entries[i].entryctrl = tk.Text(itementryframe, width=e.width if e.width else 20, height=e.height if e.height else 5, wrap="word")
78637863 if e.value is not None:
7864- self.entries[i].entryctrl.insert(tk.END, e.value)
7864+ self.entries[i].entryctrl.insert(tk.END, e.value.strip())
78657865 if e.validation_rx or e.required:
78667866 e.entryctrl.bind("<KeyRelease>", self.validate_all)
78677867 elif self.entries[i].entry_type is not None and self.entries[i].entry_type.lower() == "inputfile":
@@ -7971,7 +7971,9 @@
79717971 continue_btn.grid(column=1, row=0, sticky=tk.E, padx=3)
79727972 if not has_textarea:
79737973 self.win.bind("<Return>", self.do_continue)
7974+ self.win.bind("<F5>", self.do_continue)
79747975 self.win.bind("<Control-Return>", self.do_continue)
7976+ self.win.bind("<Shift-Return>", self.do_continue)
79757977 # Put the frames and other widgets in place.
79767978 msgframe.grid(column=0, row=0, sticky=tk.EW)
79777979 entryframe.grid(column=0, row=1, sticky=tk.EW)
@@ -8042,8 +8044,8 @@
80428044 if e.entry_type is None or e.entry_type.lower() != "checkbox":
80438045 if e.entry_type is not None and e.entry_type.lower() == "textarea":
80448046 value = e.entryctrl.get('1.0', 'end')
8045- if len(value) > 0 and value[-1] == '\n':
8046- value = value[:-1]
8047+ if len(value) > 0:
8048+ value = value.strip()
80478049 else:
80488050 value = e.entryvar.get()
80498051 if value == "":
@@ -8069,8 +8071,8 @@
80698071 for e in self.entries:
80708072 if e.entry_type is not None and e.entry_type.lower() == 'textarea':
80718073 e.value = e.entryctrl.get("1.0", 'end')
8072- if len(e.value) > 0 and e.value[-1] == '\n':
8073- e.value = e.value[:-1]
8074+ if len(e.value) > 0:
8075+ e.value = e.value.strip()
80748076 else:
80758077 e.value = e.entryvar.get()
80768078 self.win.destroy()
@@ -11887,6 +11889,89 @@
1188711889 #===============================================================================================
1188811890 #----- CONDITIONAL TESTS FOR METACOMMANDS
1188911891
11892+
11893+def xf_contains(**kwargs):
11894+ s1 = kwargs["string1"]
11895+ s2 = kwargs["string2"]
11896+ if kwargs["ignorecase"] and kwargs["ignorecase"].lower() == "i":
11897+ s1 = s1.lower()
11898+ s2 = s2.lower()
11899+ return s2 in s1
11900+
11901+conditionallist.add(r'^\s*CONTAINS\s*\(\s*(?P<string1>[^ )]+)\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_contains)
11902+conditionallist.add(r'^\s*CONTAINS\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_contains)
11903+conditionallist.add(r'^\s*CONTAINS\s*\(\s*(?P<string1>[^ )]+)\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_contains)
11904+conditionallist.add(r"^\s*CONTAINS\s*\(\s*(?P<string1>[^ )]+)\s*,\s*'(?P<string2>[^']+)'\s*(?:\s*,\s*(?P<ignorecase>I))?\)", xf_contains)
11905+conditionallist.add(r"^\s*CONTAINS\s*\(\s*(?P<string1>[^ )]+)\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11906+conditionallist.add(r"^\s*CONTAINS\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11907+conditionallist.add(r"^\s*CONTAINS\s*\(\s*'(?P<string1>[^']+)'\s*,\s*(?P<string2>[^ )]+)\s*(?:\s*,\s*(?P<ignorecase>I))?\)", xf_contains)
11908+conditionallist.add(r'^\s*CONTAINS\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_contains)
11909+conditionallist.add(r"^\s*CONTAINS\s*\(\s*'(?P<string1>[^']+)'\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11910+conditionallist.add(r"^\s*CONTAINS\s*\(\s*'(?P<string1>[^']+)'\s*,\s*\"(?P<string2>[^\"]+)\"(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11911+conditionallist.add(r"^\s*CONTAINS\s*\(\s*\"(?P<string1>[^\"]+)\"\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11912+conditionallist.add(r"^\s*CONTAINS\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11913+conditionallist.add(r'^\s*CONTAINS\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_contains)
11914+conditionallist.add(r'^\s*CONTAINS\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_contains)
11915+conditionallist.add(r"^\s*CONTAINS\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11916+conditionallist.add(r"^\s*CONTAINS\s*\(\s*'(?P<string1>[^']+)'\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_contains)
11917+
11918+
11919+
11920+
11921+
11922+def xf_startswith(**kwargs):
11923+ s1 = kwargs["string1"]
11924+ s2 = kwargs["string2"]
11925+ if kwargs["ignorecase"] and kwargs["ignorecase"].lower() == "i":
11926+ s1 = s1.lower()
11927+ s2 = s2.lower()
11928+ return s1[:len(s2)] == s2
11929+
11930+conditionallist.add(r'^\s*STARTS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_startswith)
11931+conditionallist.add(r'^\s*STARTS_WITH\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_startswith)
11932+conditionallist.add(r'^\s*STARTS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_startswith)
11933+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*'(?P<string2>[^']+)'\s*(?:\s*,\s*(?P<ignorecase>I))?\)", xf_startswith)
11934+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11935+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11936+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*(?P<string2>[^ )]+)\s*(?:\s*,\s*(?P<ignorecase>I))?\)", xf_startswith)
11937+conditionallist.add(r'^\s*STARTS_WITH\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_startswith)
11938+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11939+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*\"(?P<string2>[^\"]+)\"(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11940+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*\"(?P<string1>[^\"]+)\"\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11941+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11942+conditionallist.add(r'^\s*STARTS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_startswith)
11943+conditionallist.add(r'^\s*STARTS_WITH\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_startswith)
11944+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11945+conditionallist.add(r"^\s*STARTS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_startswith)
11946+
11947+
11948+
11949+def xf_endswith(**kwargs):
11950+ s1 = kwargs["string1"]
11951+ s2 = kwargs["string2"]
11952+ if kwargs["ignorecase"] and kwargs["ignorecase"].lower() == "i":
11953+ s1 = s1.lower()
11954+ s2 = s2.lower()
11955+ return s1[-len(s2):] == s2
11956+
11957+conditionallist.add(r'^\s*ENDS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_endswith)
11958+conditionallist.add(r'^\s*ENDS_WITH\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_endswith)
11959+conditionallist.add(r'^\s*ENDS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_endswith)
11960+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*'(?P<string2>[^']+)'\s*(?:\s*,\s*(?P<ignorecase>I))?\)", xf_endswith)
11961+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*(?P<string1>[^ )]+)\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11962+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*(?P<string2>[^ )]+)(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11963+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*(?P<string2>[^ )]+)\s*(?:\s*,\s*(?P<ignorecase>I))?\)", xf_endswith)
11964+conditionallist.add(r'^\s*ENDS_WITH\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_endswith)
11965+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11966+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*\"(?P<string2>[^\"]+)\"(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11967+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*\"(?P<string1>[^\"]+)\"\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11968+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11969+conditionallist.add(r'^\s*ENDS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*"(?P<string2>[^"]+)"(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_endswith)
11970+conditionallist.add(r'^\s*ENDS_WITH\s*\(\s*"(?P<string1>[^"]+)"\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)', xf_endswith)
11971+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*`(?P<string1>[^`]+)`\s*,\s*'(?P<string2>[^']+)'(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11972+conditionallist.add(r"^\s*ENDS_WITH\s*\(\s*'(?P<string1>[^']+)'\s*,\s*`(?P<string2>[^`]+)`(?:\s*,\s*(?P<ignorecase>I))?\s*\)", xf_endswith)
11973+
11974+
1189011975 def xf_hasrows(**kwargs):
1189111976 queryname = kwargs["queryname"]
1189211977 sql = u"select count(*) from %s;" % queryname
@@ -12473,7 +12558,7 @@
1247312558
1247412559
1247512560 def read_sqlfile(sql_file_name):
12476- # Read lines from the given script file, create a list of SqlCmd objects,
12561+ # Read lines from the given script file, create a list of ScriptCmd objects,
1247712562 # and append the list to the top of the stack of script commands.
1247812563 # The filename (fn) and line number are stored with each command.
1247912564 # Arguments:
diff -r c13ded0ca50b -r f98ba2c7f7e8 setup.py
--- a/setup.py Sat Jan 08 06:37:54 2022 -0800
+++ b/setup.py Sun Jan 09 08:06:30 2022 -0800
@@ -5,7 +5,7 @@
55 long_description = f.read()
66
77 setuptools.setup(name='execsql',
8- version='1.96.1',
8+ version='1.97.0',
99 description="Runs a SQL script against a PostgreSQL, MS-Access, SQLite, MS-SQL-Server, MySQL, MariaDB, Firebird, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables. Data can be exported in 18 different formats, including CSV, TSV, ODS, HTML, JSON, LaTeX, and Markdown tables, and using custom templates.",
1010 author='Dreas Nielsen',
1111 author_email='dreas.nielsen@gmail.com',
diff -r c13ded0ca50b -r f98ba2c7f7e8 templates/script_template.sql
--- a/templates/script_template.sql Sat Jan 08 06:37:54 2022 -0800
+++ b/templates/script_template.sql Sun Jan 09 08:06:30 2022 -0800
@@ -11,7 +11,7 @@
1111 --
1212 --
1313 -- COPYRIGHT
14--- Copyright (c) 2021,
14+-- Copyright (c) 2022,
1515 --
1616 -- AUTHORS
1717 --
@@ -186,7 +186,7 @@
186186 -- !x! sub run_tag 0!!run_tag!!
187187 -- !x! endif
188188 -- !x! if(!!do_run_numbering!!)
189- -- !x! sub output_dir !!data_dir!!!!$pathsep!!Run_!!run_tag!!_!!$date_tag!!
189+ -- !x! sub output_dir !!output_parent!!!!$pathsep!!Run_!!run_tag!!_!!$date_tag!!
190190 -- !x! sub logfile logfiles!!$pathsep!!Run_!!run_tag!!_logfile.txt
191191 -- !x! if(!!get_run_description!!)
192192 -- !x! prompt enter_sub run_description message "Please enter a description for this run (!!run_tag!!)"
Afficher sur ancien navigateur de dépôt.