diff --git a/.github/workflows/cgatapps_python.yml b/.github/workflows/cgatapps_python.yml
index 115bc011b..44dde74b3 100644
--- a/.github/workflows/cgatapps_python.yml
+++ b/.github/workflows/cgatapps_python.yml
@@ -47,3 +47,4 @@ jobs:
pip install nose
nosetests -v tests/test_style.py
nosetests -v tests/test_scripts.py
+ nosetests -v tests/test_import.py
\ No newline at end of file
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 000000000..e41dd8d21
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,73 @@
+name: Publish cgat-apps wheels to PyPI and TestPyPI
+
+on:
+ push:
+ branches:
+ - v[0-9]+.[0-9]+.x
+ tags:
+ - v*
+ release:
+ types:
+ - published
+
+jobs:
+
+ build_sdist:
+
+ runs-on: ${{ matrix.os }}-latest
+ strategy:
+ matrix:
+ os: [ubuntu, macos]
+ python-version: [3.10]
+
+ steps:
+ - name: Checkout cgat-apps
+ uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install prerequisite Python libraries
+ run: |
+ python -m pip install --upgrade pip
+ pip install wheel
+
+ - name: Create source distribution
+ run: python setup.py sdist bdist_wheel
+
+ - uses: actions/upload-artifact@v2
+ with:
+ path: dist/*.tar.gz
+
+ - uses: actions/upload-artifact@v2
+ with:
+ path: dist/*.whl
+
+ upload_pypi:
+
+ needs: [build_sdist]
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Get Artifacts
+ uses: actions/download-artifact@v2
+ with:
+ name: artifact
+ path: dist
+
+ - name: Publish distribution to Test PyPI
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+ uses: pypa/gh-action-pypi-publish@master
+ with:
+ user: __token__
+ password: ${{ secrets.TEST_PYPI_API_TOKEN }}
+ repository_url: https://test.pypi.org/legacy/
+
+ - name: Publish distribution to PyPI
+ if: github.event_name == 'release' && github.event.action == 'published'
+ uses: pypa/gh-action-pypi-publish@master
+ with:
+ password: ${{ secrets.PYPI_API_TOKEN }}
+
diff --git a/COPYING b/COPYING
deleted file mode 100644
index e38022ec0..000000000
--- a/COPYING
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright 2010-2011 The Medical Research Council (MRC). All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-1. Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
-notice, this list of conditions and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE MRC ``AS IS'' AND ANY EXPRESS OR
-IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE MRC OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index 583ea0cc1..000000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,22 +0,0 @@
-Metadata-Version: 1.1
-Name: SphinxReport
-Version: 2.0
-Summary: SphinxReport : a report generator in python based on Sphinx and matplotlib
-Home-page: http://code.google.com/p/sphinx-report/
-Author: Andreas Heger
-Author-email: andreas.heger@gmail.com
-License: BSD
-Description: SphinxReport : a report generator in python based on Sphinx and matplotlib
-Keywords: report generator sphinx matplotlib sql
-Platform: any
-Classifier: Development Status :: 3 - Alpha
-Classifier: Intended Audience :: Science/Research
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved
-Classifier: Programming Language :: Python
-Classifier: Topic :: Software Development
-Classifier: Topic :: Scientific/Engineering
-Classifier: Operating System :: Microsoft :: Windows
-Classifier: Operating System :: POSIX
-Classifier: Operating System :: Unix
-Classifier: Operating System :: MacOS
diff --git a/cgat/AGP.py b/cgat/AGP.py
index 305b1b16b..809b3366a 100644
--- a/cgat/AGP.py
+++ b/cgat/AGP.py
@@ -9,7 +9,22 @@
"""
+import functools
+
+def deprecated_class(cls):
+ orig_init = cls.__init__
+
+ @functools.wraps(orig_init)
+ def new_init(self, *args, **kwargs):
+ warnings.warn(f"{cls.__name__} is deprecated and will be removed in the next release.", DeprecationWarning)
+ orig_init(self, *args, **kwargs)
+
+ cls.__init__ = new_init
+ return cls
+
+
+@deprecated_class
class ObjectPosition(object):
def map(self, start, end):
@@ -19,6 +34,7 @@ def map(self, start, end):
return end + self.start, start + self.start
+@deprecated_class
class AGP(object):
"""Parser for AGP formatted files."""
diff --git a/cgat/CBioPortal.py b/cgat/CBioPortal.py
deleted file mode 100644
index f85f47115..000000000
--- a/cgat/CBioPortal.py
+++ /dev/null
@@ -1,1038 +0,0 @@
-'''CBioPortal.py - Interface with the Sloan-Kettering cBioPortal webservice
-========================================================================
-
-The Sloan Kettering cBioPortal webservice provides access to a
-database of results of genomics experiments on various cancers. The
-database is organised into studies, each study contains a number of
-case lists, where each list contains the ids of a set of patients, and
-genetic profiles, each of which represents an assay conducted on the
-patients in the case list as part of the study.
-
-The main class here is the CBioPortal class representing a connection
-to the cBioPortal Database. Query's are represented as methods of the
-class. Study ids or names or case lists can be provided to the
-constructor to the object, via the setDefaultStudy and
-setDefaultCaseList methods or to the indevidual query methods. Where
-ever possible the validity of parameters is checked *before* the query
-is executed.
-
-Whenever a query requires a genetic profile id or a list of such ids,
-but none are given, the list of all profiles for which the
-show_in_analysis flag is set will be used.
-
-All of the commands provided in the webservice are implemented here
-and as far as possible the name, syntax and paramter names of the
-query are identical to the raw commands to the webservice. These
-queries are:
-
-* getCancerStudies,
-* getCaseLists,
-* getProfileData,
-* getMutationData,
-* getClinicalData,
-* getProteinArrayInfo,
-* getProteinArrayData,
-* getLink,
-* getOncoprintHTML.
-
-In addition two new queries are implememented that are not part of the
-webservice:
-
-* getPercentAltered and
-* getTotalAltered
-
-These emulate the function of the website where the percent of cases
-that show any alteration for the gene and profiles given are returned
-(getPercentAltered, or the percent of cases that show an alteration in
-any of the genes (getTotalAltered) is returned.
-
-examples::
-
- gene_list = [ "TP53",
- "BCL2",
- "MYC" ]
- portal = CBioPortal()
- portal.setDefaultStudy(study = "prad_mskcc")
- portal.setDefaultCaseList(case_set_id = "prad_all_complete")
- portal.getPercentAltered(gene_list = gene_list)
-
-or more tersely::
-
- portal.CBioProtal()
- portal.getPercentAltered(study = "prad_mskcc", case_set_id = "prad_all_complete",
- gene_list = ["TP53","BCL2","MYC"],
- genetic_profile_id =["prad_mskcc_mrna"])
-
-Any warnings returned by the query are stored in CBioPortal.last_warnings.
-
-Query's that would give too long an URL are split into smaller querys
-and the results combined transparently.
-
-A commandline interface is provided for convenience, syntax::
-
- python CBioPortal.py [options] command(s)
-
-Reference
----------
-
-'''
-from urllib.request import urlopen
-import re
-import optparse
-import sys
-from cgatcore import iotools as iotools
-from collections import OrderedDict as odict
-from cgatcore import experiment as E
-
-
-class CBioPortal():
- """connect to the cBioPortal Database.
-
- If no url is specified the default url is used. A list of of valid
- study ids is retrieved from the database. This both confirms that
- the datavase is reachable, and provides cached checking for the
- ids provided. If a study or study name is provided then this is
- set as the defualt study for this session and the details of the
- availible profiles and cases is retrieved. 'Study' is the study
- id. If both study and study_name are specified then the study id
- is used.
- """
-
- url = "http://www.cbioportal.org/public-portal/webservice.do"
-
- def __init__(self, url=None, study=None, study_name=None,
- case_list_id=None):
-
- if url:
- self.url = url
-
- self.getCancerStudies()
- self.study = None
- self.case_list = None
-
- if study:
-
- if study in self._valid_study_ids:
- self.study = study
-
- self.profiles = self.getGeneticProfiles(study)
- self.cases = self.getCaseLists(study)
- else:
- raise ValueError("%s is not a valid study id" % study)
-
- elif study_name:
- if study_name in [x['name'] for x in cancer_studies]:
- study = [x['cancer_study_id']
- for x in cancer_studies if x['name'] == study_name][0]
- self.study = study
- self.profiles = self.getGeneticProfiles(study)
- self.cases = self.getCaseLists(study)
-
- else:
- raise ValueError("%s is not a valid study name" % study_name)
-
- if case_list_id:
- self.setDefaultCaseList(case_list_id)
-
- def _executeQuery(self, command, args=None):
- """execute the provided command on the database.
-
- args are specified as a dictionary. error checking on the
- results is performed here and the returned value is a list of
- lists with each list representing a row of the returned table.
- """
- try:
- argument_string = "&".join(["%s=%s" % (x, args[x]) for x in args])
- command_string = "&".join([command, argument_string])
- except TypeError:
- command_string = command
-
- query = "%s?cmd=%s" % (self.url, command_string)
-
- if len(query) > 2048:
- if "gene_list" in args:
- genes = args['gene_list'].split(",")
-
- if len(genes) < 2:
- raise ValueError("Request too long")
-
- args['gene_list'] = ",".join(genes[(len(genes) / 2):])
- query1 = self._executeQuery(command, args)
- warnings = self.last_warnings
-
- args['gene_list'] = ",".join(genes[:(len(genes) / 2)])
- query2 = self._executeQuery(command, args)
- self.last_warnings = warnings + self.last_warnings
-
- return query1 + query2
-
- data = urlopen(query)
-
- line = data.readline()
- self.last_query = query
- self.last_status = line
- self.last_warnings = []
- self.last_header = [self.last_status]
- return_table = []
-
- while re.match("^#", line):
-
- if re.match("^# Warning: (.+)", line):
- self.last_warnings.append(
- re.match("^# Warning: (.+)", line).groups(1)[0])
- self.last_header.append(line)
- line = data.readline()
- continue
-
- elif re.match("^#", line):
- self.last_header.append(line)
- line = data.readline()
- continue
-
- if re.match("^Error: (.+)", line):
- self.last_header.append(line)
- raise CDGSError(re.match("^Error: (.+)", line).groups(1)[0], query)
- line = line.strip()
- headers = line.split("\t")
-
- for line in data:
- if re.match("^# Warning: (.+)", line):
- self.last_warnings.append(
- re.match("^# Warning: (.+)", line).groups(1)[0])
- self.last_header.append(line)
- line = data.readline()
- continue
- line = line.strip()
- return_table.append(odict(list(zip(headers, line.split("\t")))))
-
- return return_table
-
- def _getStudyId(self, study, study_name):
-
- if study:
- if study in self._valid_study_ids:
- return study
- else:
- raise ValueError("%s is not a valid study id" % study)
-
- elif study_name:
- name_lookup = [x['study']
- for x in self.cancer_studies if x['name'] == study_name]
- if len(name_lookup) == 1:
- return name_lookup[0]
- else:
- raise ValueError(
- "Cannot find study id for study '%s'" % study_name)
-
- elif self.study:
- return self.study
-
- else:
- return None
-
- def getCancerStudies(self):
- """Fetches the list of cancer studies currently in the database.
-
- Returns list of dictionaries with three entries
- 'cancer_study_id','name' and 'description'. Also caches this
- data to verify the validity of later calls
- """
- cancer_studies = self._executeQuery("getCancerStudies")
- self.cancer_studies = cancer_studies
- self._valid_study_ids = [x['cancer_study_id'] for x in cancer_studies]
-
- return self.cancer_studies
-
- def getGeneticProfiles(self, study=None, study_name=None):
- """Fetches the valid genetic profiles for a particular study.
-
- study is the study id. If both study and study_name are
- specified, study is used. If neither study nor study name is
- specified then the default study is used if set, if not a
- value error is raised.
- Returns a list of dictionaries
- """
-
- study = self._getStudyId(study, study_name)
-
- if not study:
- raise ValueError("A study must be specified if no default is set")
-
- genetic_studies = self._executeQuery(command="getGeneticProfiles",
- args=dict(cancer_study_id=study))
- return genetic_studies
-
- def getCaseLists(self, study=None, study_name=None):
- """Retrieves meta-data regarding all case lists stored about a
- specific cancer study.
-
- For example, a within a particular study, only some cases may
- have sequence data, and another subset of cases may have been
- sequenced and treated with a specific therapeutic
- protocol. Multiple case lists may be associated with each
- cancer study, and this method enables you to retrieve
- meta-data regarding all of these case lists.
-
- Data is returned as a list of dictionaries with the following
- entries:
-
- * case_list_id: a unique ID used to identify the case list ID
- in subsequent interface calls. This is a human readable
- ID. For example, "gbm_all" identifies all cases profiles in
- the TCGA GBM study.
-
- * case_list_name: short name for the case list.
-
- * case_list_description: short description of the case list.
-
- * cancer_study_id: cancer study ID tied to this genetic
- profile. Will match the input cancer_study_id.
-
- * case_ids: space delimited list of all case IDs that make up
- this case list.
- """
-
- study = self._getStudyId(study, study_name)
-
- if not study:
- raise ValueError("A study must be specified if no default is set")
-
- case_lists = self._executeQuery(
- command="getCaseLists",
- args=dict
- ({'cancer_study_id': study}))
- return case_lists
-
- def getProfileData(self, gene_list, case_set_id=None,
- genetic_profile_id=None, study=None, study_name=None):
- """Retrieves genomic profile data for one or more genes.
-
- You can specify one gene and many profiles or one profile and
- many genes. If you specify no genetic profiles then all
- genetic profiles for the specified or default study are used
- if the case_set_id is from that study otherwise a ValueError
- is raised.
-
- Return value depends on the parameters. If you specify a
- single genetic profile and multiple genes a list of ordered
- dictionaries with the following entries::
-
- gene_id: Entrez Gene ID
- common: HUGO Gene Symbol
- entries 3 - N: Data for each case
-
- If you specify multi genetic profiles and a single gene, a
- list of ordered dictoraries with the following entries is
- returned::
-
- genetic_profile_id: The Genetic Profile ID.
- alteration_type: The Genetic Alteration Type, e.g. MUTATION, MUTATION_EXTENDED, COPY_NUMBER_ALTERATION, or MRNA_EXPRESSION.
- gene_id: Entrez Gene ID.
- common: HUGO Gene Symbol.
- Columns 5 - N: Data for each case.
-
- """
-
- # Do some pre-query checking.
-
- if len(gene_list) > 1 and len(genetic_profile_id) > 1:
- raise ValueError(
- "%i genes and %i profiles specified\n.Please "
- "specify either one gene many profiles or one "
- "profile many genes" % (
- len(gene_list), len(genetic_profile_id)))
-
- study_id = self._getStudyId(study, study_name)
- case_set_id = self._getCaseListId(case_set_id, study_id)
-
- genetic_profile_id = self._getAndCheckGeneticProfiles(
- genetic_profile_id=genetic_profile_id, study=study)
-
- gene_list = ",".join(gene_list)
- genetic_profile_id = ",".join(genetic_profile_id)
-
- profile_data = self._executeQuery(
- command="getProfileData",
- args={'case_set_id': case_set_id,
- 'genetic_profile_id': genetic_profile_id,
- 'gene_list': gene_list})
-
- return profile_data
-
- def getMutationData(self, gene_list, genetic_profile_id, case_set_id=None,
- study=None, study_name=None):
- '''For data of type EXTENDED_MUTATION, you can request the full set of
- annotated extended mutation data.
-
- This enables you to, for example, determine which sequencing
- center sequenced the mutation, the amino acid change that
- results from the mutation, or gather links to predicted
- functional consequences of the mutation.
-
- Query Format
-
-
- case_set_id= [case set ID] (required)
- genetic_profile_id= [a single genetic profile IDs] (required).
- gene_list= [one or more genes, specified as HUGO Gene Symbols or
- Entrez Gene IDs](required)
-
- Response Format
-
- A list of dictionaries with the following entires
-
- entrez_gene_id: Entrez Gene ID.
- gene_symbol: HUGO Gene Symbol.
- case_id: Case ID.
- sequencing_center: Sequencer Center responsible for identifying
- this mutation.
- For example: broad.mit.edu.
- mutation_status: somatic or germline mutation status. all mutations
- returned will be of type somatic.
- mutation_type: mutation type, such as nonsense, missense, or frameshift_ins.
- validation_status: validation status. Usually valid, invalid, or unknown.
- amino_acid_change: amino acid change resulting from the mutation.
-
- functional_impact_score: predicted functional impact score,
- as predicted by: Mutation Assessor.
- xvar_link: Link to the Mutation Assessor web site.
- xvar_link_pdb: Link to the Protein Data Bank (PDB) View within
- Mutation Assessor web site.
- xvar_link_msa: Link the Multiple Sequence Alignment (MSA) view
- within the Mutation Assessor web site.
- chr: chromosome where mutation occurs.
- start_position: start position of mutation.
- end_position: end position of mutation.
-
- If a default study is set then a check will be performed to
- set if the supplied case id is from the specified study. The
- study can be over written using the study and study_name
- parameters
-
- '''
-
- study_id = self._getStudyId(study, study_name)
-
- case_set_id = self._getCaseListId(case_set_id, study_id)
-
- if study_id:
- if not study_id == self.study:
- profiles = getGeneticProfiles(study_id)
- else:
- profiles = self.profiles
-
- if genetic_profile_id not in proiles:
- raise ValueError(
- "%s not a valid genetic profile for study %s" %
- (genetic_profile_id, gene_id))
- genetic_profile_id = ",".join(genetic_profile_id)
- gene_list = ",".join(gene_list)
- mutation_data = self._executeQuery(
- command="getMutationData",
- args=dict({"case_set_id": case_set_id,
- "genetic_profile_id": genetic_profile_id,
- "gene_list": gene_list}))
- return mutation_data
-
- def getClinicalData(self, case_set_id=None, study=None, study_name=None):
- '''Retrieves overall survival, disease free survival and age at
- diagnosis for specified cases.
-
- Due to patient privacy restrictions, no other clinical data is
- available.
-
- Query Format
- ------------
-
- case_set_id= [case set ID] (required)
-
- Response Format
- ---------------
-
- A list of dictionaries with the following entries:
-
- case_id: Unique Case Identifier.
- overall_survival_months: Overall survival, in months.
- overall_survival_status: Overall survival status, usually
- indicated as "LIVING" or "DECEASED".
- disease_free_survival_months: Disease free survival, in months.
- disease_free_survival_status: Disease free survival status,
- usually indicated as "DiseaseFree" or "Recurred/Progressed".
- age_at_diagnosis: Age at diagnosis.
-
- If a study is specified or a defualt study is set, then the
- case_set_id will be tested to check if it exists for that
- study.
-
- '''
-
- study_id = self._getStudyId(study, study_name)
-
- case_set_id = self._getCaseListId(case_set_id, study_id)
-
- clincal_data = self._executeQuery(
- command="getClinicalData",
- args=dict({'case_set_id': case_set_id}))
- return clincal_data
-
- def getProteinArrayInfo(self, protein_array_type=None, gene_list=None,
- study=None, study_name=None):
- '''Retrieves information on antibodies used by reverse-phase protein
- arrays (RPPA) to measure protein/phosphoprotein levels.
-
- Query Format
- ------------
-
- cancer_study_id= [cancer study ID] (required)
- protein_array_type= [protein_level or phosphorylation]
- gene_list= [one or more genes, specified as HUGO Gene
- Symbols or Entrez Gene IDs].
-
- Response Format
- ---------------
-
- A list of dictionaries with the following entires:
-
- ARRAY_ID: The protein array ID.
- ARRAY_TYPE: The protein array antibody type, i.e. protein_level
- or phosphorylation.
- GENE: The targeted gene name (HUGO gene symbol).
- RESIDUE: The targeted resdue(s).
-
- If no study is specified the default study is used. If that is
- not specified an error is raised.
-
- '''
-
- study = self._getStudyId(study, study_name)
- args = dict({'cancer_study_id': study})
-
- if gene_list:
- args['gene_list'] = ",".join(gene_list)
-
- if protein_array_type:
- args['protein_array_type'] = protein_array_type
-
- protein_array_info = self._executeQuery(
- command="getProteinArrayInfo", args=args)
-
- return protein_array_info
-
- def getProteinArrayData(self, protein_array_id=None, case_set_id=None,
- array_info=0, study=None, study_name=None):
- '''Retrieves protein and/or phosphoprotein levels measured by
- reverse-phase protein arrays (RPPA).
-
- Query Format
- ------------
-
- case_set_id= [case set ID]
- protein_array_id= [one or more protein array IDs] as list.
- array_info= [1 or 0]. If 1, antibody information will also be exported.
-
- Response Format 1
- -----------------
-
- If the parameter of array_info is not specified or it is not
- 1, returns a list of dictionaries with the following columns.
-
- ARRAY_ID: The protein array ID.
- Columns 2 - N: Data for each case.
-
- Response Format 2
- -----------------
-
- If the parameter of array_info is 1, you will receive a list
- of ordered dictionaries with the following entires:
-
- ARRAY_ID: The protein array ID.
- ARRAY_TYPE: The protein array antibody type, i.e. protein_level or
- phosphorylation.
- GENE: The targeted gene name (HUGO gene symbol).
- RESIDUE: The targeted resdue(s).
- Columns 5 - N: Data for each case.
-
- If the defualt study is set then the case_set_id will be
- check. The default study can be overidden using the study or
- study_name parameters.
-
- '''
-
- study_id = self._getStudyId(study, study_name)
-
- case_set_id = self._getCaseListId(case_set_id, study_id)
- args = dict({"case_set_id": case_set_id,
- "array_info": array_info})
-
- if protein_array_id:
- args['protein_array_id'] = ",".join(protein_array_id)
-
- protein_array_data = self._executeQuery(
- command="getProteinArrayData", args=args)
-
- return protein_array_data
-
- def getLink(self, gene_list, study=None, study_name=None, report="full"):
- '''return a perminant link to the cBioPortal report for the gene_list
- cancer_study_id=[cancer study ID] gene_list=[a comma
- separated list of HUGO gene symbols] (required)
- report=[report to display; can be one of: full (default),
- oncoprint_html]
- '''
-
- study = self._getStudyId(study, study_name)
- if not study:
- raise ValueError("Study must be specified")
- if report not in ["full", "oncoprint_html"]:
- raise ValueError("%s is not a valid report" % report)
-
- url = "/".join(self.url.split("/")[:-1])
-
- gene_list = ",".join(gene_list)
- return "%s/link.do?cancer_study_id=%s&gene_list=%s&report=%s" %\
- (url, study, gene_list, report)
-
- def getOncoprintHTML(self, gene_list, study=None, study_name=None):
- '''returns the HTML for the oncoprint report for the specified gene
- list and study'''
-
- url = "/".join(self.url.split("/")[0:-1])
- gene_list = ",".join(gene_list)
- command = "%s/link.do?cancer_study_id=%s&gene_list=%s&report=oncoprint_html" % (
- url, study, gene_list)
- return urlopen(command).read()
-
- def setDefaultStudy(self, study=None, study_name=None):
- '''sets a new study as the default study. Will check that the study
- id is valid'''
- study = self._getStudyId(study, study_name)
- self.study = study
- self.profiles = self.getGeneticProfiles()
- self.cases = self.getCaseLists()
-
- all_case_list = [x['case_list_id']
- for x in self.cases
- if x['case_list_name'] == "All Tumours"]
-
- if len(all_case_list) == 1:
- self.setDefaultCaseList(all_case_list)
-
- def setDefaultCaseList(self, case_set_id, study=None, study_name=None):
- '''set the default case list. If study is not specified the default
- study will be used.
-
- The study will be used to check that the case_set exists.
- '''
-
- study = self._getStudyId(study, study_name)
- case_list = self._getCaseListId(case_set_id, study=study)
-
- if not study == self.study:
- self.setDefaultStudy(study)
-
- self.case_list = case_list
-
- def _getCaseListId(self, case_set_id=None, study=None, strict=True):
- ''' checking is only done if study is specified or a default is set '''
-
- study_id = self._getStudyId(study, None)
-
- if case_set_id:
- if study_id:
- if not study_id == self.study:
- case_lists = [x['case_list_id']
- for x in self.getCaseLists(study_id)]
- else:
- case_lists = [x['case_list_id'] for x in self.cases]
-
- if case_set_id not in case_lists:
- raise ValueError(
- "%s is not a valid case list for study %s" % (case_set_id, study_id))
- return case_set_id
-
- else:
- if self.case_list:
- return self.case_list
- else:
- raise ValueError("No case_set_id provided and no default set")
-
- def _getAndCheckGeneticProfiles(self, genetic_profile_id=None, study=None):
-
- study_id = self._getStudyId(study, None)
- if not genetic_profile_id:
- if not study_id:
- raise ValueError(
- "Either genetic_profile_id or study must be specified")
-
- if study_id == self.study:
- genetic_profile_id = [x['genetic_profile_id']
- for x in self.profiles
- if x['show_profile_in_analysis_tab'] == "true"]
- else:
- genetic_profile_id = [x['genetic_profile_id']
- for x in self.getGeneticProfiles(study_id)
- if x['show_profile_in_analysis_tab'] == "true"]
-
- return genetic_profile_id
-
- else:
-
- if not study_id:
- return genetic_profile_id
-
- if study_id == self.study:
- genetic_profile_id = [x['genetic_profile_id'] for x in self.profiles
- if x['genetic_profile_id'] in genetic_profile_id]
- else:
- genetic_profile_id = [x['genetic_profile_id'] for x in self.getGeneticProfiles(study_id)
- if x['genetic_profile_id'] in genetic_profile_id]
-
- if len(genetic_profile_id) == 0:
- raise ValueError("no valid genetic_profile_ids found")
-
- return genetic_profile_id
-
- def getPercentAltered(self, gene_list, study=None, study_name=None,
- case_set_id=None, genetic_profile_id=None,
- threshold=2):
- '''Get the percent of cases that have one or more of the specified
- alterations for each gene
-
- Query Format
- ------------
-
- study = [cancer_study_id] The study to use.
-
- study_name = [cancer_study_name] The name of the study to
- use. If neither this nor study are specified,
- then the default is used.
-
- case_set_id = [case_set_id] The case list to use. If not
- specified, the default case list is used.
-
- gene_list = [one or more genes, specified as HUGO Gene Symobls
- or ENtrez Gene IDs] (require)
-
- genetic_profile_id = [one or more genetic profile IDs] If none
- specified all genetic profiles for the specified study are
- used..
-
- threhold = [z_score_threshold] the numeric threshold at which
- a mrna expression z-score is said to be significant.
-
- Response Format
- ---------------
-
- A list of dictionaries with the following entries
- gene_id: The Entrez Gene ID
- common: The Hugo Gene Symbol
- altered_in: The percent of cases in which the gene is altered
-
- One implementation note is that a guess must be made as to
- wether a returned profile value represents a alteration or
- not. Currently guesses are only made for copy number
- variation, mrna expression and mutionation
-
- '''
-
- study = self._getStudyId(study, study_name)
- case_set_id = self._getCaseListId(case_set_id, study)
- genetic_profile_id = self._getAndCheckGeneticProfiles(
- genetic_profile_id, study)
-
- if study and study == self.study:
- profiles = self.profiles
- case_list = [x['case_ids']
- for x in self.cases
- if x['case_list_id'] == case_set_id][0]
-
- else:
- profiles = self.getGeneticProfiles(study)
- case_list = [x['case_ids'] for x in self.getCaseLists(
- study) if x['case_list_id'] == case_set_id][0]
-
- return_table = []
- case_list = case_list.split(" ")
-
- data = []
- warnings = []
- for profile in genetic_profile_id:
-
- data.append(self.getProfileData(
- gene_list=gene_list, case_set_id=case_set_id,
- genetic_profile_id=[profile]))
- warnings.extend(self.last_warnings)
-
- # data[profile][gene][case]
- for gene in range(len(data[0])):
- cases_altered = 0.0
-
- geneProfile = [x[gene] for x in data]
-
- for case in (set(geneProfile[0]) - set(["gene_id", "common"])):
- if len([geneProfile[x][case] for x in range(len(geneProfile))
- if self._guessAlteration(
- geneProfile[x][case],
- genetic_profile_id[x], profiles)]) > 0:
-
- cases_altered += 1
-
- return_table.append(
- dict({'gene_id': geneProfile[0]['GENE_ID'],
- 'common': geneProfile[0]['COMMON'],
- 'altered_in': cases_altered * 100 / len(case_list)}))
-
- self.last_warnings = warnings
- return return_table
-
- def getTotalAltered(self, gene_list, study=None, study_name=None, case_set_id=None, genetic_profile_id=None, threshold=2):
- ''' Calculate the percent of cases in which any one of the specified genes are altered '''
-
- study = self._getStudyId(study, study_name)
- case_set_id = self._getCaseListId(case_set_id, study)
- genetic_profile_id = self._getAndCheckGeneticProfiles(
- genetic_profile_id, study)
-
- if study == self.study:
- profiles = self.profiles
- case_list = [x['case_ids']
- for x in self.cases if x['case_list_id'] == case_set_id][0]
- else:
- profiles = self.getGeneticProfiles(study)
- case_list = [x['case_ids'] for x in self.getCaseLists(
- study) if x['case_list_id'] == case_set_id][0]
- case_list = case_list.split(" ")
-
- data = []
-
- # data[gene][profile][case]
- cases_altered = 0.0
- for gene in gene_list:
- data.append(self.getProfileData(
- gene_list=[gene], case_set_id=case_set_id,
- genetic_profile_id=genetic_profile_id))
- # catch special case where only a single
- # genetic_profile_id is passed in, so the query is single
- # gene and single profile and returns genes as columns
- # format.
- if len(genetic_profile_id) == 1 and len(data[0]) == 1:
- data[0][0]['GENETIC_PROFILE_ID'] = genetic_profile_id[0]
-
- for case_id in set(data[0][0]) - set(
- ["GENETIC_PROFILE_ID", "ALTERATION_TYPE",
- "GENE_ID", "COMMON"]):
-
- case_altered = False
- for gene in data:
-
- altered = len([x for x in gene
- if self._guessAlteration(x[case_id],
- x['GENETIC_PROFILE_ID'],
- profiles, threshold)])
- if (altered > 0):
- case_altered = True
-
- if case_altered is True:
- cases_altered += 1.0
-
- return cases_altered * 100 / len(case_list)
-
- def _guessAlteration(self, value, genetic_profile_id, genetic_profiles,
- threshold=2):
-
- alteration_type = [x['genetic_alteration_type']
- for x in genetic_profiles
- if x['genetic_profile_id'] == genetic_profile_id][0]
-
- # print alteration_type
- if alteration_type == "COPY_NUMBER_ALTERATION":
- if value == "0" or value == "-1" or value == "1":
- return False
- elif value == "NaN":
- return False
- else:
- return True
-
- elif alteration_type == "MRNA_EXPRESSION":
- if re.search("[^0-9\.\-]", value):
- # is character string
- return False
- elif re.search("[0-9]+\.[0-9]+", value):
- # is float
- value = float(value)
- if abs(value) > threshold:
- return True
- else:
- return False
- else: # must be int?
- # print value
- if value == "0":
- return False
- else:
- return True
-
- elif alteration_type == "METHYLATION":
- return False
-
- elif (alteration_type == "MUTATION" or
- alteration_type == "MUTATION_EXTENDED"):
- if value == "NaN":
- return False
- else:
- return True
- else:
- return False
-
-
-class CDGSError(Exception):
-
- '''exception that handles errors returned by querys in the database'''
-
- def __init__(self, error, request):
- self.error = error
- self.request = request
-
- def __str__(self):
- return "Request %s return error:\n%s" % (self.request, self.error)
-
-
-def tableToString(intable):
-
- headers = "\t".join([x for x in intable[0]])
-
- line_list = []
- for line in intable:
-
- line_list.append("\t".join([str(line[x]) for x in line]))
-
- outTable = "\n".join(line_list)
- outTable = headers + "\n" + outTable
-
- return outTable
-
-
-def main(argv=None):
-
- parser = E.OptionParser(
- version="%prog version: $Id$",
- usage=globals()["__doc__"])
-
- parser.add_option("-o", "--output_file", type="string", default=None,
- help="[Optional] Filename to output results to. [default=STDOUT]")
- parser.add_option("-u", "--url", type="string", default="http://www.cbioportal.org/public-portal/webservice.do",
- help="[Optional] Url to the cBioPortal webservice [default=%default]")
-
- cqueryopts = optparse.OptionGroup(
- parser, "Common parameters", "Common arguments to the query")
- cqueryopts.add_option("-s", "--study_id", dest="study_id", type="string", default=None,
- help="[Required/OPtional] cBioPortal ID for study [default=%default].\n This or study_name required for: getGeneticProfiles, getCaseLists, getProteinArrayInfo, getLink,getOncoprintHTML, getPercentAltered, getTotalAltered")
- cqueryopts.add_option("-n", "--study_name", dest="study_name", type="string", default=None,
- help="[Required/Optional] cBioPortal Name for study [defualt=%default].\n See above for which commands require this.")
- cqueryopts.add_option("-c", "--case_set_id", dest="case_set_id", type="string", default=None,
- help="[Required for some] cBioPortal case_set_id specifying the case list to use.\nRequired for getProfileData, getMutationData, getClincalData, getProteinArrayData, getPercentAltered, getTotalAltered. Default is case_set_id for case list 'All Tumours' ")
- cqueryopts.add_option("-g", "--gene_list", dest="gene_list", type="string", default=None,
- help="[Required for some] Comma seperated list of HUGO gene symbols or Entrez gene IDs.\nRequired for getProfileData, getMutationData, getLink, getOncoprintHTML")
- cqueryopts.add_option("-f", "--gene_list_file", dest="gene_list_file", type="string", default=None,
- help="[Optional] Filename to read in gene_list from")
- cqueryopts.add_option("-p", "--profile_id", dest="profile_id", type="string",
- help="[Optional] Comma seperated list of cBioPortal genetic_profile_ids. If none are specified then the list of profiles for the study where display in analysis is True is used.")
-
- squeryopts = optparse.OptionGroup(
- parser, "Query specific parameters", "Arguments specific to a particular query")
- squeryopts.add_option("--protein_array_type", dest="protein_array_type", type="string", default="protein_level",
- help="[Optional] Either protein_level or phosphorylation [default=%default]")
- squeryopts.add_option("--protein_array_id", dest="protein_array_id", type="string",
- help="[Required for some] comma seperated list of one or more protein array IDs")
- squeryopts.add_option("--array_info", dest="protein_array_info", type="int", default=0,
- help="[Optional] If 1, antibody infomation will also be exported in a getProteinArrayData query [default=%default]")
- squeryopts.add_option("--output-report", dest="report", type="string", default="full",
- help="[Optional] Report type to display for getLink. Either full or oncoprint_html [default=%default] ")
- squeryopts.add_option("--threshold", dest="threshold", type="int", default=2,
- help="[Optional] Threshold for deciding if an alteration is significant for continuous metrics [default=%default]")
-
- parser.add_option_group(cqueryopts)
- parser.add_option_group(squeryopts)
-
- (options, args) = E.Start(
- parser, add_pipe_options=False, add_output_options=False, argv=argv)
-
- portal = CBioPortal(url=options.url, study=options.study_id,
- study_name=options.study_name, case_list_id=options.case_set_id)
-
- results = []
-
- if options.gene_list_file:
- infile = iotools.open_file(options.gene_list_file)
- gene_list = [x.strip() for x in infile]
- elif options.gene_list:
- gene_list = options.gene_list.split(",")
-
- if options.profile_id:
- profile_id = options.profile_id.split(",")
- else:
- profile_id = None
-
- if "getCancerStudies" in args:
- results.append(portal.getCancerStudies())
-
- if "getGeneticProfiles" in args:
- results.append(portal.getGeneticProfiles())
-
- if "getCaseLists" in args:
- results.append(portal.getCaseLists())
-
- if "getProfileData" in args:
- results.append(
- portal.getProfileData(gene_list=gene_list,
- genetic_profile_id=profile_id))
-
- if "getMutationData" in args:
- results.append(
- portal.getMutationData(gene_list=gene_list,
- genetic_profile_id=profile_id))
-
- if "getClinicalData" in args:
- results.append(portal.getClinicalData())
-
- if "getProteinArrayInfo" in args:
- results.append(portal.getProteinArrayInfo(
- gene_list=gene_list,
- protein_array_type=options.protein_array_type))
-
- if "getProteinArrayData" in args:
- results.append(portal.getProteinArrayData(
- protein_array_id=options.protein_array_id,
- array_info=options.array_info))
-
- if "getPercentAltered" in args:
- results.append(portal.getPercentAltered(
- gene_list=gene_list, genetic_profile_id=profile_id,
- threshold=options.threshold))
-
- if "getLink" in args:
- results.append(
- portal.getLink(gene_list=gene_list, report=options.report))
-
- if "getOncoprintHTML" in args:
- results.append(portal.getOncoprintHTML(gene_list=gene_list))
-
- if len(results) == 0:
- sys.stderr.write("No recognised query commands provided")
- sys.exit()
-
- if options.output_file:
- outf = iotools.open_file(options.output_file, "w")
- else:
- outf = sys.stdout
-
- for result in results:
- try:
- outf.write(tableToString(result))
- except:
- outf.write(result)
-
- E.Stop()
-
-
-if __name__ == "__main__":
- sys.exit(main(sys.argv))
diff --git a/cgat/IGV.py b/cgat/IGV.py
deleted file mode 100644
index ebf5053e8..000000000
--- a/cgat/IGV.py
+++ /dev/null
@@ -1,169 +0,0 @@
-'''
-IGV.py - Simple wrapper to the IGV socket interface
-====================================================
-
-:Tags: Python
-
-This code was written by Brent Pedersen.
-
-Downloaded from https://github.com/brentp/bio-playground/blob/master/igv/igv.py
-on Nov.30 2011.
-
-'''
-
-import socket
-import os.path as op
-import os
-import subprocess
-import time
-
-
-def startIGV(command="igv.sh", port=None):
- """start IGV on a specific port."""
- args = [command]
- if port is not None:
- args.extend(['-p', str(port)])
- process = subprocess.Popen(args,
- stdout=subprocess.PIPE)
- time.sleep(10)
- return process
-
-
-class IGV(object):
- r"""
- Simple wrapper to the IGV (http://www.broadinstitute.org/software/igv/home)
- socket interface (http://www.broadinstitute.org/software/igv/PortCommands)
-
- requires:
-
- 1) you have IGV running on your machine (launch with webstart here:
- http://www.broadinstitute.org/software/igv/download)
-
- 2) you have enabled port communication in
- View -> Preferences... -> Advanced
-
- Successful commands return 'OK'
-
- example usage:
-
- >>> igv = IGV()
- >>> igv.genome('hg19')
- 'OK'
-
- >>> igv.load('http://www.broadinstitute.org/igvdata/1KG/pilot2Bams/NA12878.SLX.bam')
- 'OK'
- >>> igv.go('chr1:45,600-45,800')
- 'OK'
-
- #save as svg, png, or jpg
- >>> igv.save('/tmp/r/region.svg')
- 'OK'
- >>> igv.save('/tmp/r/region.png')
- 'OK'
-
- # go to a gene name.
- >>> igv.go('muc5b')
- 'OK'
- >>> igv.sort()
- 'OK'
- >>> igv.save('muc5b.png')
- 'OK'
-
- # get a list of commands that will work as an IGV batch script.
- >>> print "\n".join(igv.commands)
- snapshotDirectory /tmp/igv
- genome hg19
- goto chr1:45,600-45,800
- snapshotDirectory /tmp/r
- snapshot region.svg
- snapshot region.png
- goto muc5b
- sort base
- snapshot muc5b.png
-
- Note, there will be some delay as the browser has to load the annotations
- at each step.
-
- """
- _socket = None
- _path = None
-
- def __init__(self, host='127.0.0.1', port=60151, snapshot_dir='/tmp/igv'):
- self.host = host
- self.port = port
- self.commands = []
- self.connect()
- self.set_path(snapshot_dir)
-
- @classmethod
- def start(cls, jnlp="igv.jnlp",
- url="http://www.broadinstitute.org/igv/projects/current/"):
- import subprocess
- p = subprocess.Popen("/usr/bin/javaws -Xnosplash %s%s" % (url, jnlp),
- shell=True, stdout=subprocess.PIPE)
- p.wait()
- return p.returncode
-
- def connect(self):
- if self._socket:
- self._socket.close()
- self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._socket.connect((self.host, self.port))
-
- def go(self, position):
- return self.send('goto ' + position)
- goto = go
-
- def genome(self, name):
- return self.send('genome ' + name)
-
- def load(self, url):
- return self.send('load ' + url)
-
- def sort(self, option='base'):
- """
- options is one of: base, position, strand, quality, sample, and
- readGroup.
- """
- assert option in ("base", "position", "strand", "quality", "sample",
- "readGroup")
- return self.send('sort ' + option)
-
- def set_path(self, snapshot_dir):
- if snapshot_dir == self._path:
- return
- if not op.exists(snapshot_dir):
- os.makedirs(snapshot_dir)
-
- self.send('snapshotDirectory %s' % snapshot_dir)
- self._path = snapshot_dir
-
- def expand(self, track):
- self.send('expand %s' % track)
-
- def collapse(self, track):
- self.send('collapse %s' % track)
-
- def clear(self):
- self.send('clear')
-
- def send(self, cmd):
- self.commands.append(cmd)
- self._socket.send(cmd + '\n')
- return self._socket.recv(10).rstrip('\n')
-
- def save(self, path=None):
- if path is not None:
- # igv assumes the path is just a single filename, but
- # we can set the snapshot dir. then just use the filename.
- dirname = op.dirname(path)
- if dirname:
- self.set_path(dirname)
- return self.send('snapshot ' + op.basename(path))
- else:
- return self.send('snapshot')
- snapshot = save
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/cgat/SVGdraw.py b/cgat/SVGdraw.py
deleted file mode 100644
index aee891935..000000000
--- a/cgat/SVGdraw.py
+++ /dev/null
@@ -1,1155 +0,0 @@
-'''
-SVGdraw.py - generate SVG drawings
-======================================================
-
-:Tags: Python
-
-This module has been copied from 3rd party resources.
-
-SVGdraw uses an object model drawing and a method toXML to create SVG graphics
-by using easy to use classes and methods usualy you start by creating a drawing eg
-
- d=drawing()
- #then you create a SVG root element
- s=svg()
- #then you add some elements eg a circle and add it to the svg root element
- c=circle()
- #you can supply attributes by using named arguments.
- c=circle(fill='red',stroke='blue')
- #or by updating the attributes attribute:
- c.attributes['stroke-width']=1
- s.addElement(c)
- #then you add the svg root element to the drawing
- d.setSVG(s)
- #and finaly you xmlify the drawing
- d.toXml()
-
-
-this results in the svg source of the drawing, which consists of a circle
-on a white background. Its as easy as that;)
-This module was created using the SVG specification of www.w3c.org and the
-O'Reilly (www.oreilly.com) python books as information sources. A svg viewer
-is available from www.adobe.com
-'''
-
-from io import StringIO
-import sys
-
-__version__ = "1.0"
-
-use_dom_implementation = 0
-
-if use_dom_implementation != 0:
- try:
- from xml.dom import implementation
- from xml.dom.ext import PrettyPrint
- except:
- raise ImportError("PyXML is required for using the dom implementation")
-
-
-sys.setrecursionlimit = 50
-
-
-def _escape(data, entities={}):
- """Escape &, <, and > in a string of data.
-
- You can escape other strings of data by passing a dictionary as
- the optional entities parameter. The keys and values must all be
- strings; each key will be replaced with its corresponding value.
- """
- data = data.replace("&", "&")
- data = data.replace("<", "<")
- data = data.replace(">", ">")
- for chars, entity in list(entities.items()):
- data = data.replace(chars, entity)
- return data
-
-
-def _quoteattr(data, entities={}):
- """Escape and quote an attribute value.
-
- Escape &, <, and > in a string of data, then quote it for use as
- an attribute value. The \" character will be escaped as well, if
- necessary.
-
- You can escape other strings of data by passing a dictionary as
- the optional entities parameter. The keys and values must all be
- strings; each key will be replaced with its corresponding value.
- """
- data = _escape(data, entities)
- if '"' in data:
- if "'" in data:
- data = '"%s"' % data.replace('"', """)
- else:
- data = "'%s'" % data
- else:
- data = '"%s"' % data
- return data
-
-
-def _xypointlist(a):
- """formats a list of xy pairs"""
- s = ''
- for e in a: # this could be done more elegant
- s += str(e)[1:-1] + ' '
- return s
-
-
-def _viewboxlist(a):
- """formats a tuple"""
- s = ''
- for e in a:
- s += str(e) + ' '
- return s
-
-
-def _pointlist(a):
- """formats a list of numbers"""
- return str(a)[1:-1]
-
-
-class pathdata:
-
- """class used to create a pathdata object which can be used for a path.
- although most methods are pretty straightforward it might be useful to look at the SVG specification."""
- # I didn't test the methods below.
-
- def __init__(self, x=None, y=None):
- self.path = []
- if x is not None and y is not None:
- self.path.append('M ' + str(x) + ' ' + str(y))
-
- def closepath(self):
- """ends the path"""
- self.path.append('z')
-
- def move(self, x, y):
- """move to absolute"""
- self.path.append('M ' + str(x) + ' ' + str(y))
-
- def relmove(self, x, y):
- """move to relative"""
- self.path.append('m ' + str(x) + ' ' + str(y))
-
- def line(self, x, y):
- """line to absolute"""
- self.path.append('L ' + str(x) + ' ' + str(y))
-
- def relline(self, x, y):
- """line to relative"""
- self.path.append('l ' + str(x) + ' ' + str(y))
-
- def hline(self, x):
- """horizontal line to absolute"""
- self.path.append('H' + str(x))
-
- def relhline(self, x):
- """horizontal line to relative"""
- self.path.append('h' + str(x))
-
- def vline(self, y):
- """verical line to absolute"""
- self.path.append('V' + str(y))
-
- def relvline(self, y):
- """vertical line to relative"""
- self.path.append('v' + str(y))
-
- def bezier(self, x1, y1, x2, y2, x, y):
- """bezier with xy1 and xy2 to xy absolut"""
- self.path.append('C' + str(x1) + ',' + str(y1) + ' ' +
- str(x2) + ',' + str(y2) + ' ' + str(x) + ',' + str(y))
-
- def relbezier(self, x1, y1, x2, y2, x, y):
- """bezier with xy1 and xy2 to xy relative"""
- self.path.append('c' + str(x1) + ',' + str(y1) + ' ' +
- str(x2) + ',' + str(y2) + ' ' + str(x) + ',' + str(y))
-
- def smbezier(self, x2, y2, x, y):
- """smooth bezier with xy2 to xy absolut"""
- self.path.append(
- 'S' + str(x2) + ',' + str(y2) + ' ' + str(x) + ',' + str(y))
-
- def relsmbezier(self, x2, y2, x, y):
- """smooth bezier with xy2 to xy relative"""
- self.path.append(
- 's' + str(x2) + ',' + str(y2) + ' ' + str(x) + ',' + str(y))
-
- def qbezier(self, x1, y1, x, y):
- """quadratic bezier with xy1 to xy absolut"""
- self.path.append(
- 'Q' + str(x1) + ',' + str(y1) + ' ' + str(x) + ',' + str(y))
-
- def relqbezier(self, x1, y1, x, y):
- """quadratic bezier with xy1 to xy relative"""
- self.path.append(
- 'q' + str(x1) + ',' + str(y1) + ' ' + str(x) + ',' + str(y))
-
- def smqbezier(self, x, y):
- """smooth quadratic bezier to xy absolut"""
- self.path.append('T' + str(x) + ',' + str(y))
-
- def relsmqbezier(self, x, y):
- """smooth quadratic bezier to xy relative"""
- self.path.append('t' + str(x) + ',' + str(y))
-
- def ellarc(self, rx, ry, xrot, laf, sf, x, y):
- """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy absolut"""
- self.path.append('A' + str(rx) + ',' + str(ry) + ' ' + str(xrot) +
- ' ' + str(laf) + ' ' + str(sf) + ' ' + str(x) + ' ' + str(y))
-
- def relellarc(self, rx, ry, xrot, laf, sf, x, y):
- """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy relative"""
- self.path.append('a' + str(rx) + ',' + str(ry) + ' ' + str(xrot) +
- ' ' + str(laf) + ' ' + str(sf) + ' ' + str(x) + ' ' + str(y))
-
- def __repr__(self):
- return ' '.join(self.path)
-
-
-class SVGelement:
-
- """SVGelement(type,attributes,elements,text,namespace,**args)
- Creates a arbitrary svg element and is intended to be subclassed not used on its own.
- This element is the base of every svg element it defines a class which resembles
- a xml-element. The main advantage of this kind of implementation is that you don't
- have to create a toXML method for every different graph object. Every element
- consists of a type, attribute, optional subelements, optional text and an optional
- namespace. Note the elements==None, if elements = None:self.elements=[] construction.
- This is done because if you default to elements=[] every object has a reference
- to the same empty list."""
-
- def __init__(self, type='', attributes=None, elements=None, text='', namespace='', cdata=None, **args):
- self.type = type
- if attributes is None:
- self.attributes = {}
- else:
- self.attributes = attributes
- if elements is None:
- self.elements = []
- else:
- self.elements = elements
- self.text = text
- self.namespace = namespace
- self.cdata = cdata
- for arg in list(args.keys()):
- self.attributes[arg] = args[arg]
-
- def addElement(self, SVGelement):
- """adds an element to a SVGelement
-
- SVGelement.addElement(SVGelement)
- """
- self.elements.append(SVGelement)
-
- def toXml(self, level, f):
- f.write('\t' * level)
- f.write('<' + self.type)
- for attkey in list(self.attributes.keys()):
- f.write(' ' + _escape(str(attkey)) + '=' +
- _quoteattr(str(self.attributes[attkey])))
- if self.namespace:
- f.write(' xmlns="' + _escape(str(self.namespace)) + '" ')
- # added by Andreas Heger
- f.write(
- ' xmlns:xlink="' + _escape(str("http://www.w3.org/1999/xlink")) + '" ')
- if self.elements or self.text or self.cdata:
- f.write('>')
- if self.elements:
- f.write('\n')
- for element in self.elements:
- element.toXml(level + 1, f)
- if self.cdata:
- f.write('\n' + '\t' * (level + 1) + '\n')
- if self.text:
- if isinstance(self.text, str): # If the text is only text
- f.write(_escape(str(self.text)))
- else: # If the text is a spannedtext class
- f.write(str(self.text))
- if self.elements:
- f.write('\t' * level + '' + self.type + '>\n')
- elif self.text:
- f.write('' + self.type + '>\n')
- elif self.cdata:
- f.write('\t' * level + '' + self.type + '>\n')
- else:
- f.write('/>\n')
-
-
-class tspan(SVGelement):
-
- """ts=tspan(text='',**args)
-
- a tspan element can be used for applying formatting to a textsection
- usage:
- ts=tspan('this text is bold')
- ts.attributes['font-weight']='bold'
- st=spannedtext()
- st.addtspan(ts)
- t=text(3,5,st)
- """
-
- def __init__(self, text=None, **args):
- SVGelement.__init__(self, 'tspan', **args)
- if self.text is not None:
- self.text = text
-
- def __repr__(self):
- s = "'
- s += self.text
- s += ''
- return s
-
-
-class tref(SVGelement):
-
- """tr=tref(link='',**args)
-
- a tref element can be used for referencing text by a link to its id.
- usage:
- tr=tref('#linktotext')
- st=spannedtext()
- st.addtref(tr)
- t=text(3,5,st)
- """
-
- def __init__(self, link, **args):
- SVGelement.__init__(self, 'tref', {'xlink:href': link}, **args)
-
- def __repr__(self):
- s = "'
- return s
-
-
-class spannedtext:
-
- """st=spannedtext(textlist=[])
-
- a spannedtext can be used for text which consists of text, tspan's and tref's
- You can use it to add to a text element or path element. Don't add it directly
- to a svg or a group element.
- usage:
-
- ts=tspan('this text is bold')
- ts.attributes['font-weight']='bold'
- tr=tref('#linktotext')
- tr.attributes['fill']='red'
- st=spannedtext()
- st.addtspan(ts)
- st.addtref(tr)
- st.addtext('This text is not bold')
- t=text(3,5,st)
- """
-
- def __init__(self, textlist=None):
- if textlist is None:
- self.textlist = []
- else:
- self.textlist = textlist
-
- def addtext(self, text=''):
- self.textlist.append(text)
-
- def addtspan(self, tspan):
- self.textlist.append(tspan)
-
- def addtref(self, tref):
- self.textlist.append(tref)
-
- def __repr__(self):
- s = ""
- for element in self.textlist:
- s += str(element)
- return s
-
-
-class rect(SVGelement):
-
- """r=rect(width,height,x,y,fill,stroke,stroke_width,**args)
-
- a rectangle is defined by a width and height and a xy pair
- """
-
- def __init__(self, x=None, y=None, width=None, height=None, fill=None, stroke=None, stroke_width=None, **args):
- if width is None or height is None:
- if width is not None:
- raise ValueError('height is required')
- if height is not None:
- raise ValueError('width is required')
- else:
- raise ValueError('both height and width are required')
- SVGelement.__init__(
- self, 'rect', {'width': width, 'height': height}, **args)
- if x is not None:
- self.attributes['x'] = x
- if y is not None:
- self.attributes['y'] = y
- if fill is not None:
- self.attributes['fill'] = fill
- if stroke is not None:
- self.attributes['stroke'] = stroke
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
-
-
-class ellipse(SVGelement):
-
- """e=ellipse(rx,ry,x,y,fill,stroke,stroke_width,**args)
-
- an ellipse is defined as a center and a x and y radius.
- """
-
- def __init__(self, cx=None, cy=None, rx=None, ry=None, fill=None, stroke=None, stroke_width=None, **args):
- if rx is None or ry is None:
- if rx is not None:
- raise ValueError('rx is required')
- if ry is not None:
- raise ValueError('ry is required')
- else:
- raise ValueError('both rx and ry are required')
- SVGelement.__init__(self, 'ellipse', {'rx': rx, 'ry': ry}, **args)
- if cx is not None:
- self.attributes['cx'] = cx
- if cy is not None:
- self.attributes['cy'] = cy
- if fill is not None:
- self.attributes['fill'] = fill
- if stroke is not None:
- self.attributes['stroke'] = stroke
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
-
-
-class circle(SVGelement):
-
- """c=circle(x,y,radius,fill,stroke,stroke_width,**args)
-
- The circle creates an element using a x, y and radius values eg
- """
-
- def __init__(self, cx=None, cy=None, r=None, fill=None, stroke=None, stroke_width=None, **args):
- if r is None:
- raise ValueError('r is required')
- SVGelement.__init__(self, 'circle', {'r': r}, **args)
- if cx is not None:
- self.attributes['cx'] = cx
- if cy is not None:
- self.attributes['cy'] = cy
- if fill is not None:
- self.attributes['fill'] = fill
- if stroke is not None:
- self.attributes['stroke'] = stroke
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
-
-
-class point(circle):
-
- """p=point(x,y,color)
-
- A point is defined as a circle with a size 1 radius. It may be more efficient to use a
- very small rectangle if you use many points because a circle is difficult to render.
- """
-
- def __init__(self, x, y, fill='black', **args):
- circle.__init__(self, x, y, 1, fill, **args)
-
-
-class line(SVGelement):
-
- """l=line(x1,y1,x2,y2,stroke,stroke_width,**args)
-
- A line is defined by a begin x,y pair and an end x,y pair
- """
-
- def __init__(self, x1=None, y1=None, x2=None, y2=None, stroke=None, stroke_width=None, **args):
- SVGelement.__init__(self, 'line', **args)
- if x1 is not None:
- self.attributes['x1'] = x1
- if y1 is not None:
- self.attributes['y1'] = y1
- if x2 is not None:
- self.attributes['x2'] = x2
- if y2 is not None:
- self.attributes['y2'] = y2
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
- if stroke is not None:
- self.attributes['stroke'] = stroke
-
-
-class polyline(SVGelement):
-
- """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args)
-
- a polyline is defined by a list of xy pairs
- """
-
- def __init__(self, points, fill=None, stroke=None, stroke_width=None, **args):
- SVGelement.__init__(
- self, 'polyline', {'points': _xypointlist(points)}, **args)
- if fill is not None:
- self.attributes['fill'] = fill
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
- if stroke is not None:
- self.attributes['stroke'] = stroke
-
-
-class polygon(SVGelement):
-
- """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args)
-
- a polygon is defined by a list of xy pairs
- """
-
- def __init__(self, points, fill=None, stroke=None, stroke_width=None, **args):
- SVGelement.__init__(
- self, 'polygon', {'points': _xypointlist(points)}, **args)
- if fill is not None:
- self.attributes['fill'] = fill
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
- if stroke is not None:
- self.attributes['stroke'] = stroke
-
-
-class path(SVGelement):
-
- """p=path(path,fill,stroke,stroke_width,**args)
-
- a path is defined by a path object and optional width, stroke and fillcolor
- """
-
- def __init__(self, pathdata, fill=None, stroke=None, stroke_width=None, id=None, **args):
- SVGelement.__init__(self, 'path', {'d': str(pathdata)}, **args)
- if stroke is not None:
- self.attributes['stroke'] = stroke
- if fill is not None:
- self.attributes['fill'] = fill
- if stroke_width is not None:
- self.attributes['stroke-width'] = stroke_width
- if id is not None:
- self.attributes['id'] = id
-
-
-class text(SVGelement):
-
- """t=text(x,y,text,font_size,font_family,**args)
-
- a text element can bge used for displaying text on the screen
- """
-
- def __init__(self, x=None, y=None, text=None, font_size=None, font_family=None, text_anchor=None, font_style=None, **args):
- SVGelement.__init__(self, 'text', **args)
- if x is not None:
- self.attributes['x'] = x
- if y is not None:
- self.attributes['y'] = y
- if font_size is not None:
- self.attributes['font-size'] = font_size
- if font_family is not None:
- self.attributes['font-family'] = font_family
- if font_style is not None:
- self.attributes['font-style'] = font_style
- if text is not None:
- self.text = text
- if text_anchor is not None:
- self.attributes['text-anchor'] = text_anchor
-
-
-class textpath(SVGelement):
-
- """tp=textpath(text,link,**args)
-
- a textpath places a text on a path which is referenced by a link.
- """
-
- def __init__(self, link, text=None, **args):
- SVGelement.__init__(self, 'textPath', {'xlink:href': link}, **args)
- if text is not None:
- self.text = text
-
-
-class pattern(SVGelement):
-
- """p=pattern(x,y,width,height,patternUnits,**args)
-
- A pattern is used to fill or stroke an object using a pre-defined
- graphic object which can be replicated ("tiled") at fixed intervals
- in x and y to cover the areas to be painted.
- """
-
- def __init__(self, x=None, y=None, width=None, height=None, patternUnits=None, **args):
- SVGelement.__init__(self, 'pattern', **args)
- if x is not None:
- self.attributes['x'] = x
- if y is not None:
- self.attributes['y'] = y
- if width is not None:
- self.attributes['width'] = width
- if height is not None:
- self.attributes['height'] = height
- if patternUnits is not None:
- self.attributes['patternUnits'] = patternUnits
-
-
-class title(SVGelement):
-
- """t=title(text,**args)
-
- a title is a text element. The text is displayed in the title bar
- add at least one to the root svg element
- """
-
- def __init__(self, text=None, **args):
- SVGelement.__init__(self, 'title', **args)
- if text is not None:
- self.text = text
-
-
-class description(SVGelement):
-
- """d=description(text,**args)
-
- a description can be added to any element and is used for a tooltip
- Add this element before adding other elements.
- """
-
- def __init__(self, text=None, **args):
- SVGelement.__init__(self, 'desc', **args)
- if text is not None:
- self.text = text
-
-
-class lineargradient(SVGelement):
-
- """lg=lineargradient(x1,y1,x2,y2,id,**args)
-
- defines a lineargradient using two xy pairs.
- stop elements van be added to define the gradient colors.
- """
-
- def __init__(self, x1=None, y1=None, x2=None, y2=None, id=None, **args):
- SVGelement.__init__(self, 'linearGradient', **args)
- if x1 is not None:
- self.attributes['x1'] = x1
- if y1 is not None:
- self.attributes['y1'] = y1
- if x2 is not None:
- self.attributes['x2'] = x2
- if y2 is not None:
- self.attributes['y2'] = y2
- if id is not None:
- self.attributes['id'] = id
-
-
-class radialgradient(SVGelement):
-
- """rg=radialgradient(cx,cy,r,fx,fy,id,**args)
-
- defines a radial gradient using a outer circle which are defined by a cx,cy and r and by using a focalpoint.
- stop elements van be added to define the gradient colors.
- """
-
- def __init__(self, cx=None, cy=None, r=None, fx=None, fy=None, id=None, **args):
- SVGelement.__init__(self, 'radialGradient', **args)
- if cx is not None:
- self.attributes['cx'] = cx
- if cy is not None:
- self.attributes['cy'] = cy
- if r is not None:
- self.attributes['r'] = r
- if fx is not None:
- self.attributes['fx'] = fx
- if fy is not None:
- self.attributes['fy'] = fy
- if id is not None:
- self.attributes['id'] = id
-
-
-class stop(SVGelement):
-
- """st=stop(offset,stop_color,**args)
-
- Puts a stop color at the specified radius
- """
-
- def __init__(self, offset, stop_color=None, **args):
- SVGelement.__init__(self, 'stop', {'offset': offset}, **args)
- if stop_color is not None:
- self.attributes['stop-color'] = stop_color
-
-
-class style(SVGelement):
-
- """st=style(type,cdata=None,**args)
-
- Add a CDATA element to this element for defing in line stylesheets etc..
- """
-
- def __init__(self, type, cdata=None, **args):
- SVGelement.__init__(self, 'style', {'type': type}, cdata=cdata, **args)
-
-
-class image(SVGelement):
-
- """im=image(url,width,height,x,y,**args)
-
- adds an image to the drawing. Supported formats are .png, .jpg and .svg.
- """
-
- def __init__(self, url, x=None, y=None, width=None, height=None, **args):
- if width is None or height is None:
- if width is not None:
- raise ValueError('height is required')
- if height is not None:
- raise ValueError('width is required')
- else:
- raise ValueError('both height and width are required')
- SVGelement.__init__(
- self, 'image', {'xlink:href': url, 'width': width, 'height': height}, **args)
- if x is not None:
- self.attributes['x'] = x
- if y is not None:
- self.attributes['y'] = y
-
-
-class cursor(SVGelement):
-
- """c=cursor(url,**args)
-
- defines a custom cursor for a element or a drawing
- """
-
- def __init__(self, url, **args):
- SVGelement.__init__(self, 'cursor', {'xlink:href': url}, **args)
-
-
-class marker(SVGelement):
-
- """m=marker(id,viewbox,refX,refY,markerWidth,markerHeight,**args)
-
- defines a marker which can be used as an endpoint for a line or other pathtypes
- add an element to it which should be used as a marker.
- """
-
- def __init__(self, id=None, viewBox=None, refx=None, refy=None, markerWidth=None, markerHeight=None, **args):
- SVGelement.__init__(self, 'marker', **args)
- if id is not None:
- self.attributes['id'] = id
- if viewBox is not None:
- self.attributes['viewBox'] = _viewboxlist(viewBox)
- if refx is not None:
- self.attributes['refX'] = refx
- if refy is not None:
- self.attributes['refY'] = refy
- if markerWidth is not None:
- self.attributes['markerWidth'] = markerWidth
- if markerHeight is not None:
- self.attributes['markerHeight'] = markerHeight
-
-
-class group(SVGelement):
-
- """g=group(id,**args)
-
- a group is defined by an id and is used to contain elements
- g.addElement(SVGelement)
- """
-
- def __init__(self, id=None, **args):
- SVGelement.__init__(self, 'g', **args)
- if id is not None:
- self.attributes['id'] = id
-
-
-class symbol(SVGelement):
-
- """sy=symbol(id,viewbox,**args)
-
- defines a symbol which can be used on different places in your graph using
- the use element. A symbol is not rendered but you can use 'use' elements to
- display it by referencing its id.
- sy.addElement(SVGelement)
- """
-
- def __init__(self, id=None, viewBox=None, **args):
- SVGelement.__init__(self, 'symbol', **args)
- if id is not None:
- self.attributes['id'] = id
- if viewBox is not None:
- self.attributes['viewBox'] = _viewboxlist(viewBox)
-
-
-class defs(SVGelement):
-
- """d=defs(``**args``)
-
- container for defining elements
- """
-
- def __init__(self, **args):
- SVGelement.__init__(self, 'defs', **args)
-
-
-class switch(SVGelement):
-
- """sw=switch(``**args``)
-
- Elements added to a switch element which are "switched" by the attributes
- requiredFeatures, requiredExtensions and systemLanguage.
- Refer to the SVG specification for details.
- """
-
- def __init__(self, **args):
- SVGelement.__init__(self, 'switch', **args)
-
-
-class use(SVGelement):
-
- """u=use(link,x,y,width,height,``**args``)
-
- references a symbol by linking to its id and its position, height and width
- """
-
- def __init__(self, link, x=None, y=None, width=None, height=None, **args):
- SVGelement.__init__(self, 'use', {'xlink:href': link}, **args)
- if x is not None:
- self.attributes['x'] = x
- if y is not None:
- self.attributes['y'] = y
-
- if width is not None:
- self.attributes['width'] = width
- if height is not None:
- self.attributes['height'] = height
-
-
-class link(SVGelement):
-
- """a=link(url,``**args``)
-
- a link is defined by a hyperlink. add elements which have to be linked
- a.addElement(SVGelement)
- """
-
- def __init__(self, link='', **args):
- SVGelement.__init__(self, 'a', {'xlink:href': link}, **args)
-
-
-class view(SVGelement):
-
- """v=view(id,``**args``)
-
- a view can be used to create a view with different attributes"""
-
- def __init__(self, id=None, **args):
- SVGelement.__init__(self, 'view', **args)
- if id is not None:
- self.attributes['id'] = id
-
-
-class script(SVGelement):
-
- """sc=script(type,type,cdata,``**args``)
-
- adds a script element which contains CDATA to the SVG drawing
-
- """
-
- def __init__(self, type, cdata=None, **args):
- SVGelement.__init__(
- self, 'script', {'type': type}, cdata=cdata, **args)
-
-
-class animate(SVGelement):
-
- """an=animate(attribute,from,to,during,``**args``)
-
- animates an attribute.
- """
-
- def __init__(self, attribute, fr=None, to=None, dur=None, **args):
- SVGelement.__init__(
- self, 'animate', {'attributeName': attribute}, **args)
- if fr is not None:
- self.attributes['from'] = fr
- if to is not None:
- self.attributes['to'] = to
- if dur is not None:
- self.attributes['dur'] = dur
-
-
-class animateMotion(SVGelement):
-
- """an=animateMotion(pathdata,dur,``**args``)
-
- animates a SVGelement over the given path in dur seconds
- """
-
- def __init__(self, pathdata, dur, **args):
- SVGelement.__init__(self, 'animateMotion', **args)
- if pathdata is not None:
- self.attributes['path'] = str(pathdata)
- if dur is not None:
- self.attributes['dur'] = dur
-
-
-class animateTransform(SVGelement):
-
- """antr=animateTransform(type,from,to,dur,``**args``)
-
- transform an element from and to a value.
- """
-
- def __init__(self, type=None, fr=None, to=None, dur=None, **args):
- SVGelement.__init__(
- self, 'animateTransform', {'attributeName': 'transform'}, **args)
- # As far as I know the attributeName is always transform
- if type is not None:
- self.attributes['type'] = type
- if fr is not None:
- self.attributes['from'] = fr
- if to is not None:
- self.attributes['to'] = to
- if dur is not None:
- self.attributes['dur'] = dur
-
-
-class animateColor(SVGelement):
-
- """ac=animateColor(attribute,type,from,to,dur,``**args``)
-
- Animates the color of a element
- """
-
- def __init__(self, attribute, type=None, fr=None, to=None, dur=None, **args):
- SVGelement.__init__(
- self, 'animateColor', {'attributeName': attribute}, **args)
- if type is not None:
- self.attributes['type'] = type
- if fr is not None:
- self.attributes['from'] = fr
- if to is not None:
- self.attributes['to'] = to
- if dur is not None:
- self.attributes['dur'] = dur
-
-
-class set(SVGelement):
-
- """st=set(attribute,to,during,``**args``)
-
- sets an attribute to a value for a
- """
-
- def __init__(self, attribute, to=None, dur=None, **args):
- SVGelement.__init__(self, 'set', {'attributeName': attribute}, **args)
- if to is not None:
- self.attributes['to'] = to
- if dur is not None:
- self.attributes['dur'] = dur
-
-
-class svg(SVGelement):
-
- """s=svg(viewbox,width,height,``**args``)
-
- a svg or element is the root of a drawing add all elements to a svg element.
- You can have different svg elements in one svg file
- s.addElement(SVGelement)
-
- eg
- d=drawing()
- s=svg((0,0,100,100),'100%','100%')
- c=circle(50,50,20)
- s.addElement(c)
- d.setSVG(s)
- d.toXml()
- """
-
- def __init__(self, viewBox=None, width=None, height=None, **args):
- SVGelement.__init__(self, 'svg', **args)
- if viewBox is not None:
- self.attributes['viewBox'] = _viewboxlist(viewBox)
- if width is not None:
- self.attributes['width'] = width
- if height is not None:
- self.attributes['height'] = height
- self.namespace = "http://www.w3.org/2000/svg"
-
-
-class drawing:
-
- """d=drawing()
-
- this is the actual SVG document. It needs a svg element as a root.
- Use the addSVG method to set the svg to the root. Use the toXml method to write the SVG
- source to the screen or to a file
- d=drawing()
- d.addSVG(svg)
- d.toXml(optionalfilename)
- """
-
- def __init__(self):
- self.svg = None
-
- def setSVG(self, svg):
- self.svg = svg
- # Voeg een element toe aan de grafiek toe.
- if use_dom_implementation == 0:
- def toXml(self, filename='', compress=False):
- xml = StringIO()
- xml.write("\n")
- xml.write(
- "\n")
- self.svg.toXml(0, xml)
- if not filename:
- if compress:
- import gzip
- f = StringIO()
- zf = gzip.GzipFile(fileobj=f, mode='wb')
- zf.write(xml.getvalue())
- zf.close()
- f.seek(0)
- return f.read()
- else:
- return xml.getvalue()
- else:
- if filename[-4:] == 'svgz':
- import gzip
- f = gzip.GzipFile(
- filename=filename, mode="wb", compresslevel=9)
- f.write(xml.getvalue())
- f.close()
- else:
- f = file(filename, 'w')
- f.write(xml.getvalue())
- f.close()
-
- else:
- def toXml(self, filename='', compress=False):
- """drawing.toXml() ---->to the screen
- drawing.toXml(filename)---->to the file
- writes a svg drawing to the screen or to a file
- compresses if filename ends with svgz or if compress is true
- """
- doctype = implementation.createDocumentType(
- 'svg', "-//W3C//DTD SVG 1.0//EN""", 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ')
-
- global root
- # root is defined global so it can be used by the appender. Its also possible to use it as an arugument but
- # that is a bit messy.
- root = implementation.createDocument(None, None, doctype)
- # Create the xml document.
- global appender
-
- def appender(element, elementroot):
- """This recursive function appends elements to an element and sets the attributes
- and type. It stops when alle elements have been appended"""
- if element.namespace:
- e = root.createElementNS(element.namespace, element.type)
- else:
- e = root.createElement(element.type)
- if element.text:
- textnode = root.createTextNode(element.text)
- e.appendChild(textnode)
- # in element.attributes is supported from python 2.2
- for attribute in list(element.attributes.keys()):
- e.setAttribute(
- attribute, str(element.attributes[attribute]))
- if element.elements:
- for el in element.elements:
- e = appender(el, e)
- elementroot.appendChild(e)
- return elementroot
- root = appender(self.svg, root)
- if not filename:
- xml = StringIO()
- PrettyPrint(root, xml)
- if compress:
- import gzip
- f = StringIO()
- zf = gzip.GzipFile(fileobj=f, mode='wb')
- zf.write(xml.getvalue())
- zf.close()
- f.seek(0)
- return f.read()
- else:
- return xml.getvalue()
- else:
- try:
- if filename[-4:] == 'svgz':
- import gzip
- xml = StringIO()
- PrettyPrint(root, xml)
- f = gzip.GzipFile(
- filename=filename, mode='wb', compresslevel=9)
- f.write(xml.getvalue())
- f.close()
- else:
- f = open(filename, 'w')
- PrettyPrint(root, f)
- f.close()
- except:
- print("Cannot write SVG file: " + filename)
-
- def validate(self):
- try:
- import xml.parsers.xmlproc.xmlval
- except:
- raise ImportError('PyXml is required for validating SVG')
- svg = self.toXml()
- xv = xml.parsers.xmlproc.xmlval.XMLValidator()
- try:
- xv.feed(svg)
- except:
- raise ValueError("SVG is not well formed, see messages above")
- else:
- print("SVG well formed")
-
-
-if __name__ == '__main__':
-
- d = drawing()
- s = svg((0, 0, 100, 100))
- r = rect(-100, -100, 300, 300, 'cyan')
- s.addElement(r)
-
- t = title('SVGdraw Demo')
- s.addElement(t)
- g = group('animations')
- e = ellipse(0, 0, 5, 2)
- g.addElement(e)
- c = circle(0, 0, 1, 'red')
- g.addElement(c)
- pd = pathdata(0, -10)
- for i in range(6):
- pd.relsmbezier(10, 5, 0, 10)
- pd.relsmbezier(-10, 5, 0, 10)
- an = animateMotion(pd, 10)
- an.attributes['rotate'] = 'auto-reverse'
- an.attributes['repeatCount'] = "indefinite"
- g.addElement(an)
- s.addElement(g)
- for i in range(20, 120, 20):
- u = use('#animations', i, 0)
- s.addElement(u)
- for i in range(0, 120, 20):
- for j in range(5, 105, 10):
- c = circle(i, j, 1, 'red', 'black', .5)
- s.addElement(c)
- d.setSVG(s)
-
- print(d.toXml())
diff --git a/cgat/tools/gff2gff.py b/cgat/tools/gff2gff.py
index d44a8e6af..426773e79 100644
--- a/cgat/tools/gff2gff.py
+++ b/cgat/tools/gff2gff.py
@@ -3,6 +3,8 @@
:Tags: Genomics Intervals GFF Manipulation
+Note: The script can parse AGP files but this functionality
+is scheduled for deprecation in the next release.
Purpose
-------
diff --git a/cgat/version.py b/cgat/version.py
index 49e0fc1e0..a5f830a2c 100644
--- a/cgat/version.py
+++ b/cgat/version.py
@@ -1 +1 @@
-__version__ = "0.7.0"
+__version__ = "0.7.1"
diff --git a/dependency_graph/Makefile b/dependency_graph/Makefile
deleted file mode 100644
index 10856d2ab..000000000
--- a/dependency_graph/Makefile
+++ /dev/null
@@ -1,42 +0,0 @@
-NAME = cgat
-ROOT = ../scripts ../CGAT ../../CGATPipelines/CGATPipelines
-PDFS = $(NAME)_raw.pdf
-SVGS = $(NAME)_raw.svg
-CHECK = $(NAME).check
-
-
-FOOD_FLAGS?=--internal --follow
-
-.SUFFIXES: .deps .dot .pdf .svg .clusters
-
-all: $(PDFS) $(SVGS)
-
-check: $(CHECK)
-
-raw.deps: $(ROOT)
- sfood $(FOOD_FLAGS) $(ROOT) > $@
-
-$(NAME).clusters: $(ROOT)
- cd $(ROOT) ; ls -1d * > $(shell pwd)/$@
-
-$(NAME).deps: $(NAME).clusters raw.deps
- cat raw.deps | sfood-cluster -f $< > $@
-
-$(NAME)_raw.deps: raw.deps
- cp $< $@
-
-.deps.pdf:
- cat $< | python graph.py | dot -Tps | ps2pdf - $@
-
-.deps.svg:
- cat $< | python graph.py | dot -Tsvg > $@
-
-$(NAME).check:
- sfood-checker $(ROOT) >& $@
-
-clean:
- rm -f *.clusters *.dot *.pdf *.svg
- ls -1 *.deps | grep -v ^raw.deps | xargs rm -f
-
-realclean: clean
- rm -f raw.deps
diff --git a/dependency_graph/cgat_raw.svg b/dependency_graph/cgat_raw.svg
deleted file mode 100644
index 53ba6e9c1..000000000
--- a/dependency_graph/cgat_raw.svg
+++ /dev/null
@@ -1,8203 +0,0 @@
-
-
-
-
-
diff --git a/dependency_graph/graph.py b/dependency_graph/graph.py
deleted file mode 100644
index 815d76715..000000000
--- a/dependency_graph/graph.py
+++ /dev/null
@@ -1,116 +0,0 @@
-"""
-Read snakefood dependencies and output a visual graph.
-"""
-# This file is part of the Snakefood open source package.
-# See http://furius.ca/snakefood/ for licensing details.
-
-# Modified by AH to colour nodes according to subdir.
-
-import sys
-import os
-
-from snakefood.depends import read_depends, eliminate_redundant_depends
-
-prefix = '''
-# This file was generated by sfood-graph.
-
-strict digraph "dependencies" {
- graph [
- rankdir = "LR",
- overlap = "scale",
- size = "8,10",
- ratio = "fill",
- fontsize = "16",
- fontname = "Helvetica",
- clusterrank = "local"
- ]
-
- node [
- fontsize=%s
- shape=ellipse
- // style=filled
- // shape=box
- ];
-
-'''
-postfix = '''
-
-}
-'''
-
-COLORS = ["#999999", "#E69F00", "#56B4E9", "#009E73",
- "#F0E442", "#0072B2", "#D55E00", "#CC79A7"]
-
-
-def graph(pairs, write, fontsize, color_map):
- "Given (from, to) pairs of (root, fn) files, output a dot graph."
- write(prefix % fontsize)
- for (froot, f), (troot, t) in pairs:
- if opts.pythonify_filenames:
- f = normpyfn(f)
- t = normpyfn(t)
- if opts.full_pathnames:
- f = os.path.join(froot, f)
- if troot:
- t = os.path.join(troot, t)
- if troot is None:
- dn = os.path.dirname(f)
- if dn not in color_map:
- color_map[dn] = COLORS[len(color_map) % len(COLORS)]
- write('"%s" [style=filled, color="%s"];\n' % (f, color_map[dn]))
- else:
- write('"%s" -> "%s";\n' % (f, t))
- write(postfix)
-
-
-def normpyfn(fn):
- "Normalize the python filenames for output."
- if fn is None:
- return fn
- if fn.endswith('.py'):
- fn = fn[:-3]
- fn = fn.replace(os.sep, '.')
- return fn
-
-
-def main():
- import optparse
- parser = optparse.OptionParser(__doc__.strip())
-
- parser.add_option('-f', '--full-pathnames', '--full', action='store_true',
- help="Output the full pathnames, not just the relative.")
-
- parser.add_option('-p', '--pythonify-filenames', '--remove-extensions',
- action='store_true',
- help="Remove filename extensions in the graph and "
- "replace slashes with dots.")
-
- parser.add_option('-r', '--redundant', action='store_false', default=True,
- help="Do not eliminate redundant dependencies.")
-
- parser.add_option('--fontsize', action='store', type='int',
- default=10,
- help="The size of the font to use for nodes.")
-
- global opts
- opts, args = parser.parse_args()
-
- if not args:
- args = ['-']
-
- color_map = {}
-
- for fn in args:
- if fn == '-':
- f = sys.stdin
- else:
- f = open(fn)
- depends = read_depends(f)
- if opts.redundant:
- depends = eliminate_redundant_depends(depends)
- graph(depends, sys.stdout.write, opts.fontsize, color_map)
-
-
-if __name__ == "__main__":
- main()
-
diff --git a/deprecated_scripts.md b/deprecated_scripts.md
new file mode 100644
index 000000000..19051fcc8
--- /dev/null
+++ b/deprecated_scripts.md
@@ -0,0 +1,22 @@
+#Scripts that have been deprecated:
+chain2psl
+bam2UniquePairs
+bam2libtype
+bam2peakshape
+bed2annotator
+bed2plot
+cat_tables
+cgat_fasta2cDNA
+combine_tables
+csv_interestion
+csv_rename
+csv_select
+csv_set
+medip_merge_intervals
+rndomize_lines
+transfac2transfac
+
+# Warnings for deprecation in the next release
+AGP.py (its used once in gff2gff to parse agp files)
+Blat.py
+gff2psl
\ No newline at end of file
diff --git a/install.sh b/install.sh
deleted file mode 100755
index 55572178f..000000000
--- a/install.sh
+++ /dev/null
@@ -1,966 +0,0 @@
-#!/usr/bin/env bash
-
-# References
-# http://kvz.io/blog/2013/11/21/bash-best-practices/
-# http://jvns.ca/blog/2017/03/26/bash-quirks/
-
-# exit when a command fails
-set -o errexit
-
-# exit if any pipe commands fail
-set -o pipefail
-
-# exit when your script tries to use undeclared variables
-#set -o nounset
-
-# trace what gets executed
-#set -o xtrace
-
-# Bash traps
-# http://aplawrence.com/Basics/trapping_errors.html
-# https://stelfox.net/blog/2013/11/fail-fast-in-bash-scripts/
-
-set -o errtrace
-
-SCRIPT_NAME="$0"
-SCRIPT_PARAMS="$@"
-
-error_handler() {
- echo
- echo " ########################################################## "
- echo
- echo " An error occurred in:"
- echo
- echo " - line number: ${1}"
- shift
- echo " - exit status: ${1}"
- shift
- echo " - command: ${@}"
- echo
- echo " The script will abort now. User input was: "
- echo
- echo " ${SCRIPT_NAME} ${SCRIPT_PARAMS}"
- echo
- echo " Please copy and paste this error and report it via Git Hub: "
- echo " https://github.com/cgat-developers/cgat-apps/issues "
- print_env_vars
- echo " ########################################################## "
-}
-
-trap 'error_handler ${LINENO} $? ${BASH_COMMAND}' ERR INT TERM
-
-# log installation information
-log() {
- echo "# install.sh log | `hostname` | `date` | $1 "
-}
-
-# report error and exit
-report_error() {
- echo
- echo $1
- echo
- echo "Aborting."
- echo
- exit 1
-}
-
-# detect CGAT installation
-detect_cgat_installation() {
-
-if [[ -z "$CGAT_HOME" ]] ; then
-
- if [[ -d "$HOME/cgat-install/conda-install" ]] ; then
- UNINSTALL_DIR="$HOME/cgat-install"
- fi
-
-else
-
- if [[ -d "$CGAT_HOME/conda-install" ]] ; then
- UNINSTALL_DIR="$CGAT_HOME"
- fi
-
-fi
-
-} # detect_cgat_installation
-
-
-# configure environment variables
-# set: CGAT_HOME, CONDA_INSTALL_DIR, CONDA_INSTALL_TYPE_APPS
-get_cgat_env() {
-
-if [[ $TRAVIS_INSTALL ]] ; then
-
- CGAT_HOME=$TRAVIS_BUILD_DIR
- CONDA_INSTALL_TYPE_APPS="cgat-apps.yml"
- CONDA_INSTALL_TYPE_CORE="cgat-core.yml"
-
-elif [[ $JENKINS_INSTALL ]] ; then
-
- CGAT_HOME=$WORKSPACE
- CONDA_INSTALL_TYPE_APPS="cgat-apps.yml"
- CONDA_INSTALL_TYPE_CORE="cgat-core.yml"
-
-else
-
- if [[ -z $CGAT_HOME ]] ; then
- CGAT_HOME=$HOME/cgat-install
- fi
-
- if [[ $INSTALL_PRODUCTION ]] ; then
- CONDA_INSTALL_TYPE_APPS="cgat-apps.yml"
- CONDA_INSTALL_TYPE_CORE="cgat-core.yml"
- elif [[ $INSTALL_DEVEL ]] ; then
- CONDA_INSTALL_TYPE_APPS="cgat-apps.yml"
- CONDA_INSTALL_TYPE_CORE="cgat-core.yml"
- elif [[ $INSTALL_TEST ]] || [[ $INSTALL_UPDATE ]] ; then
- if [[ -d $CGAT_HOME/conda-install ]] ; then
- AUX=`find $CGAT_HOME/conda-install/envs/cgat-* -maxdepth 0`
- CONDA_INSTALL_TYPE_APPS=`basename $AUX`
- else
- echo
- echo " The location of the CGAT code was not found (function: get_cgat_env). "
- echo " Please install it first or use --location option with full path to your installation. "
- echo
- exit 1
- fi
- else
- echo
- echo " Wrong installation type! "
- echo " Installation aborted. "
- echo
- exit 1
- fi # if install type
-
-fi # if travis install
-
-CONDA_INSTALL_DIR=$CGAT_HOME/conda-install
-
-# set conda environment name
-[[ ${CONDA_INSTALL_ENV} ]] || CONDA_INSTALL_ENV="cgat-a"
-
-} # get_cgat_env
-
-
-# setup environment variables
-setup_env_vars() {
-
-export CFLAGS=$CFLAGS" -I$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/include -L$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/lib"
-export CPATH=$CPATH" -I$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/include -L$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/lib"
-export C_INCLUDE_PATH=$C_INCLUDE_PATH:$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/include
-export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/include
-export LIBRARY_PATH=$LIBRARY_PATH:$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/lib
-export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/lib:$CONDA_INSTALL_DIR/envs/$CONDA_INSTALL_ENV/lib/R/lib
-
-} # setup_env_vars
-
-# print related environment variables
-print_env_vars() {
-
-echo
-echo " Debugging: "
-echo " CFLAGS: "$CFLAGS
-echo " CPATH: "$CPATH
-echo " C_INCLUDE_PATH: "$C_INCLUDE_PATH
-echo " CPLUS_INCLUDE_PATH: "$CPLUS_INCLUDE_PATH
-echo " LIBRARY_PATH: "$LIBRARY_PATH
-echo " LD_LIBRARY_PATH: "$LD_LIBRARY_PATH
-echo " CGAT_HOME: "$CGAT_HOME
-echo " CONDA_INSTALL_DIR: "$CONDA_INSTALL_DIR
-echo " CONDA_INSTALL_TYPE_APPS: "$CONDA_INSTALL_TYPE_APPS
-echo " CONDA_INSTALL_TYPE_CORE: "$CONDA_INSTALL_TYPE_CORE
-echo " CONDA_INSTALL_ENV: "$CONDA_INSTALL_ENV
-echo " PYTHONPATH: "$PYTHONPATH
-[[ ! $INSTALL_TEST ]] && echo " INSTALL_BRANCH: "$INSTALL_BRANCH
-[[ ! $INSTALL_TEST ]] && echo " CORE_BRANCH: "$CORE_BRANCH
-[[ ! $INSTALL_TEST ]] && echo " RELEASE: "$RELEASE
-[[ ! $INSTALL_TEST ]] && echo " CODE_DOWNLOAD_TYPE: "$CODE_DOWNLOAD_TYPE
-echo
-
-} # print_env_vars
-
-# Travis installations are running out of RAM
-# with large conda installations. Issue has been submitted here:
-# https://github.com/conda/conda/issues/1197
-# While we wait for a response, we'll try to clean up the conda
-# installation folder as much as possible
-conda_cleanup() {
-conda clean --index-cache
-conda clean --lock
-conda clean --tarballs -y
-conda clean --packages -y
-}
-
-
-# helper function to install cgat-core
-install_cgat_core() {
-
-log "install cgat core"
-
-OLDWD=`pwd`
-cd $CGAT_HOME
-
-if [[ $CODE_DOWNLOAD_TYPE -eq 0 ]] ; then
- # get the latest version from Git Hub in zip format
- curl -LOk https://github.com/cgat-developers/cgat-core/archive/$CORE_BRANCH.zip
- unzip $CORE_BRANCH.zip
- rm $CORE_BRANCH.zip
- if [[ ${RELEASE} ]] ; then
- NEW_NAME=`echo $CORE_BRANCH | sed 's/^v//g'`
- mv cgat-core-$NEW_NAME/ cgat-core/
- else
- mv cgat-core-$CORE_BRANCH/ cgat-core/
- fi
-elif [[ $CODE_DOWNLOAD_TYPE -eq 1 ]] ; then
- # get latest version from Git Hub with git clone
- git clone --branch=$CORE_BRANCH https://github.com/cgat-developers/cgat-core.git
-elif [[ $CODE_DOWNLOAD_TYPE -eq 2 ]] ; then
- # get latest version from Git Hub with git clone
- git clone --branch=$CORE_BRANCH git@github.com:cgat-developers/cgat-core.git
-else
- report_error " Unknown download type for CGAT core... "
-fi
-
-cd cgat-core/
-
-# remove install_requires (no longer required with conda package)
-sed -i'' -e '/REPO_REQUIREMENT/,/pass/d' setup.py
-sed -i'' -e '/# dependencies/,/dependency_links=dependency_links,/d' setup.py
-python setup.py develop
-
-if [[ $? -ne 0 ]] ; then
- echo
- echo " There was a problem doing: 'python setup.py develop' "
- echo " Installation did not finish properly. "
- echo
- echo " Please submit this issue via Git Hub: "
- echo " https://github.com/cgat-developers/cgat-apps/issues "
- echo
- print_env_vars
-
-fi # if-$?
-
-# revert setup.py if downloaded with git
-[[ $CODE_DOWNLOAD_TYPE -ge 1 ]] && git checkout -- setup.py
-
-# go back to old working directory
-cd $OLDWD
-
-} # install_cgat_core
-
-
-# proceed with conda installation
-conda_install() {
-
-log "installing conda"
-
-detect_cgat_installation
-
-if [[ -n "$UNINSTALL_DIR" ]] ; then
-
- echo
- echo " An installation of the CGAT code was found in: $UNINSTALL_DIR"
- echo " Please use --location to install CGAT code in a different location "
- echo " or uninstall the current version before proceeding."
- echo
- echo " Installation is aborted."
- echo
- exit 1
-
-fi
-
-# get environment variables: CGAT_HOME, CONDA_INSTALL_DIR, CONDA_INSTALL_TYPE_APPS
-get_cgat_env
-
-mkdir -p $CGAT_HOME
-cd $CGAT_HOME
-
-# select Miniconda bootstrap script depending on Operating System
-MINICONDA=
-
-if [[ `uname` == "Linux" ]] ; then
-
- # Conda 4.4 breaks everything again!
- # Conda 4.5 looks better
- MINICONDA="Miniconda3-latest-Linux-x86_64.sh"
- #MINICONDA="Miniconda3-4.3.31-Linux-x86_64.sh"
-
-elif [[ `uname` == "Darwin" ]] ; then
-
- # Conda 4.4 breaks everything again!
- # Conda 4.5 looks better
- MINICONDA="Miniconda3-latest-MacOSX-x86_64.sh"
- #MINICONDA="Miniconda3-4.3.31-MacOSX-x86_64.sh"
-
-else
-
- echo
- echo " Unsupported operating system detected. "
- echo
- echo " Aborting installation... "
- echo
- exit 1
-
-fi
-
-log "downloading miniconda"
-# download and install conda
-wget https://repo.continuum.io/miniconda/${MINICONDA} -O miniconda.sh;
-
-log "installing miniconda"
-bash miniconda.sh -b -p $CONDA_INSTALL_DIR
-source ${CONDA_INSTALL_DIR}/bin/activate
-hash -r
-
-# install cgat environment
-log "updating conda environment"
-# Conda 4.4 breaks everything again!
-# Conda 4.5 looks better
-#conda install --quiet --yes 'conda=4.3.33'
-conda update --all --yes
-conda info -a
-
-log "installing CGAT environment"
-# Now using conda environment files:
-# https://conda.io/docs/using/envs.html#use-environment-from-file
-
-[[ -z ${TRAVIS_BRANCH} ]] && TRAVIS_BRANCH=${INSTALL_BRANCH}
-
-curl -o env-apps.yml -O https://raw.githubusercontent.com/cgat-developers/cgat-apps/${TRAVIS_BRANCH}/conda/environments/${CONDA_INSTALL_TYPE_APPS}
-
-curl -o env-core.yml -O https://raw.githubusercontent.com/cgat-developers/cgat-core/${CORE_BRANCH}/conda/environments/${CONDA_INSTALL_TYPE_CORE}
-
-conda env create --quiet --name ${CONDA_INSTALL_ENV} --file env-apps.yml
-conda env update --quiet --name ${CONDA_INSTALL_ENV} --file env-core.yml
-
-conda env export --name ${CONDA_INSTALL_ENV}
-
-# activate cgat environment
-source $CONDA_INSTALL_DIR/bin/activate $CONDA_INSTALL_ENV
-
-log "installing CGAT code into conda environment"
-# if installation is 'devel' (outside of travis), checkout latest version from github
-if [[ -z ${TRAVIS_INSTALL} ]] ; then
-
- DEV_RESULT=0
-
- # TO-DO: after code release, INSTALL_PRODUCTION will use pip rather than github
- if [[ $INSTALL_PRODUCTION ]] || [[ $INSTALL_DEVEL ]] || [[ $JENKINS_INSTALL ]] ; then
-
- # install extra deps
- curl -o env-extra.yml -O https://raw.githubusercontent.com/cgat-developers/cgat-apps/${TRAVIS_BRANCH}/conda/environments/apps-extra.yml
- conda env update --quiet --file env-extra.yml
- conda env export --name cgat-a
-
- # download the code out of jenkins
- if [[ -z ${JENKINS_INSTALL} ]] ; then
-
- # make sure you are in the CGAT_HOME folder
- cd $CGAT_HOME
-
- if [[ $CODE_DOWNLOAD_TYPE -eq 0 ]] ; then
- # get the latest version from Git Hub in zip format
- curl -LOk https://github.com/cgat-developers/cgat-apps/archive/$INSTALL_BRANCH.zip
- unzip $INSTALL_BRANCH.zip
- rm $INSTALL_BRANCH.zip
- if [[ ${RELEASE} ]] ; then
- NEW_NAME=`echo $INSTALL_BRANCH | sed 's/^v//g'`
- mv cgat-apps-$NEW_NAME/ cgat-apps/
- else
- mv cgat-apps-$INSTALL_BRANCH/ cgat-apps/
- fi
- elif [[ $CODE_DOWNLOAD_TYPE -eq 1 ]] ; then
- # get latest version from Git Hub with git clone
- git clone --branch=$INSTALL_BRANCH https://github.com/cgat-developers/cgat-apps.git
- elif [[ $CODE_DOWNLOAD_TYPE -eq 2 ]] ; then
- # get latest version from Git Hub with git clone
- git clone --branch=$INSTALL_BRANCH git@github.com:cgat-developers/cgat-apps.git
- else
- report_error " Unknown download type for CGAT code... "
- fi
-
- # make sure you are in the CGAT_HOME/cgat-apps folder
- cd $CGAT_HOME/cgat-apps
-
- fi
-
- # Set up other environment variables
- setup_env_vars
-
- # install cgat-core
- install_cgat_core
-
- # brute force: modify console_scripts variable/entry point for cgat command
- sed -i'' -e 's/CGATScripts/scripts/g' setup.py
-
- # Python preparation
- # remove install_requires (no longer required with conda package)
- sed -i'' -e '/REPO_REQUIREMENT/,/pass/d' setup.py
- sed -i'' -e '/# dependencies/,/dependency_links=dependency_links,/d' setup.py
- python setup.py develop
-
- if [[ $? -ne 0 ]] ; then
- echo
- echo " There was a problem doing: 'python setup.py develop' "
- echo " Installation did not finish properly. "
- echo
- echo " Please submit this issue via Git Hub: "
- echo " https://github.com/cgat-developers/cgat-apps/issues "
- echo
-
- print_env_vars
-
- fi # if-$?
-
- # revert setup.py if downloaded with git
- [[ $CODE_DOWNLOAD_TYPE -ge 1 ]] && git checkout -- setup.py
-
- # environment pinning
- # python scripts/conda.py
-
- fi # if INSTALL_DEVEL
-
- # check whether conda create went fine
- if [[ $DEV_RESULT -ne 0 ]] ; then
- echo
- echo " There was a problem installing the code with conda. "
- echo " Installation did not finish properly. "
- echo
- echo " Please submit this issue via Git Hub: "
- echo " https://github.com/cgat-developers/cgat-apps/issues "
- echo
-
- print_env_vars
-
- else
- clear
- echo
- echo " The code was successfully installed!"
- echo
- echo " To activate the CGAT environment type: "
- echo " $ source $CONDA_INSTALL_DIR/etc/profile.d/conda.sh"
- echo " $ conda activate base"
- echo " $ conda activate $CONDA_INSTALL_ENV"
- [[ $INSTALL_PRODUCTION ]] && echo " cgat --help"
- echo
- echo " To deactivate the environment, use:"
- echo " $ conda deactivate"
- echo
- fi # if-$ conda create
-
-fi # if travis install
-
-} # conda install
-
-
-# test code with conda install
-conda_test() {
-
-log "starting conda_test"
-
-# get environment variables: CGAT_HOME, CONDA_INSTALL_DIR, CONDA_INSTALL_TYPE_APPS
-get_cgat_env
-
-setup_env_vars
-
-# setup environment and run tests
-if [[ $TRAVIS_INSTALL ]] || [[ $JENKINS_INSTALL ]] ; then
-
- # enable Conda env
- log "activating CGAT conda environment"
- source $CONDA_INSTALL_DIR/bin/activate $CONDA_INSTALL_ENV
-
- # show conda environment used for testing
- conda env export
-
- # install cgat-core
- install_cgat_core
-
- # python preparation
- log "install CGAT code into conda environment"
- cd $CGAT_HOME
- # remove install_requires (no longer required with conda package)
- sed -i'' -e '/REPO_REQUIREMENT/,/pass/d' setup.py
- sed -i'' -e '/# dependencies/,/dependency_links=dependency_links,/d' setup.py
- python setup.py develop
-
- log "starting tests"
- # run nosetests
- if [[ $TEST_ALL ]] ; then
- log "test_import.py" && nosetests -v tests/test_import.py && \
- log "test_style.py" && nosetests -v tests/test_style.py && \
- echo -e "restrict:\n manifest:\n" > tests/_test_commandline.yml && \
- log "test_commandline" && nosetests -v tests/test_commandline.py && \
- log "test_scripts" && nosetests -v tests/test_scripts.py ;
- elif [[ $TEST_IMPORT ]] ; then
- nosetests -v tests/test_import.py ;
- elif [[ $TEST_STYLE ]] ; then
- nosetests -v tests/test_style.py ;
- elif [[ $TEST_CMDLINE ]] ; then
- echo -e "restrict:\n manifest:\n" > tests/_test_commandline.yml
- nosetests -v tests/test_commandline.py ;
- elif [[ $TEST_PRODUCTION_SCRIPTS ]] ; then
- echo -e "restrict:\n manifest:\n" > tests/_test_scripts.yml
- nosetests -v tests/test_scripts.py ;
- else
- nosetests -v tests/test_scripts.py ;
- fi
-
-else
-
- source $CONDA_INSTALL_DIR/bin/activate $CONDA_INSTALL_ENV
- RET=$( (conda list | grep cgat-scripts) || true )
-
- if [[ -z "${RET}" ]] ; then
- # this is "cgat-devel" so tests can be run
-
- # make sure you are in the CGAT_HOME/cgat-apps folder
- cd $CGAT_HOME/cgat-apps
-
- # remove install_requires (no longer required with conda package)
- sed -i'' -e '/REPO_REQUIREMENT/,/pass/d' setup.py
- sed -i'' -e '/# dependencies/,/dependency_links=dependency_links,/d' setup.py
- python setup.py develop
- OUTPUT_DIR=`pwd`
-
- # run tests
- /usr/bin/time -o test_import.time -v nosetests -v tests/test_import.py >& test_import.out
- if [[ $? -eq 0 ]] ; then
- echo
- echo " test_import.py passed successfully! "
- echo
- else
- echo
- echo " test_import.py failed. Please see $OUTPUT_DIR/test_import.out file for detailed output. "
- echo
-
- print_env_vars
-
- fi
-
- /usr/bin/time -o test_scripts.time -v nosetests -v tests/test_scripts.py >& test_scripts.out
- if [[ $? -eq 0 ]] ; then
- echo
- echo " test_scripts.py passed successfully! "
- echo
- else
- echo
- echo " test_scripts.py failed. Please see $OUTPUT_DIR/test_scripts.out file for detailed output. "
- echo
-
- print_env_vars
-
- fi
-
- else
- # in this case, the installation found was "cgat-scripts" so no need to run tests
- echo
- echo " You installed the cgat-scripts, which has been properly tested before. "
- echo " No need to test. Exiting now... "
- echo
-
- exit 0
- fi
-
-fi # if travis or jenkins
-
-} # conda_test
-
-
-# update conda installation
-conda_update() {
-
-# get environment variables: CGAT_HOME, CONDA_INSTALL_DIR, CONDA_INSTALL_TYPE_APPS
-get_cgat_env
-
-source $CONDA_INSTALL_DIR/bin/activate $CONDA_INSTALL_ENV
-conda update --all
-
-if [[ ! $? -eq 0 ]] ; then
-
- echo
- echo " There was a problem updating the installation. "
- echo
- echo " Please submit this issue via Git Hub: "
- echo " https://github.com/cgat-developers/cgat-apps/issues "
- echo
-
-else
-
- echo
- echo " All packages were succesfully updated. "
- echo
-
-fi
-
-} # conda_update
-
-
-# unistall CGAT code collection
-uninstall() {
-
-detect_cgat_installation
-
-if [[ -z "$UNINSTALL_DIR" ]] ; then
-
- echo
- echo " The location of the CGAT code was not found. "
- echo " Please uninstall manually."
- echo
- exit 1
-
-else
-
- rm -rf $UNINSTALL_DIR
- if [[ $? -eq 0 ]] ; then
- echo
- echo " CGAT code successfully uninstalled."
- echo
- exit 0
- else
- echo
- echo " There was a problem uninstalling the CGAT code."
- echo " Please uninstall manually."
- echo
- exit 1
- fi
-fi
-
-}
-
-
-# test whether --git and --git-ssh download is doable
-test_git() {
- git --version >& /dev/null || GIT_AVAIL=$?
- if [[ $GIT_AVAIL -ne 0 ]] ; then
- echo
- echo " Git is not available but --git or --git-ssh option was given."
- echo " Please rerun this script on a computer with git installed "
- echo " or try again without --git or --git-ssh"
- report_error " "
- fi
-}
-
-
-# test whether --git-ssh download is doable
-test_git_ssh() {
- ssh-add -L >& /dev/null || SSH_KEYS_LOADED=$?
- if [[ $SSH_KEYS_LOADED -ne 0 ]] ; then
- echo
- echo " Please load your ssh keys for GitHub before proceeding!"
- echo
- echo " Try: "
- echo " 1. eval \$(ssh-agent)"
- echo " 2. ssh-add ~/.ssh/id_rsa # or the file where your private key is"
- report_error " and run this script again. "
- fi
-}
-
-
-# don't mix branch and release options together
-test_mix_branch_release() {
- # don't mix branch and release options together
- if [[ $RELEASE ]] ; then
- if [[ "$INSTALL_BRANCH" != "master" ]] ; then
- echo
- echo " You cannot mix git branches and releases for the installation."
- echo
- echo " Your input was: "$SCRIPT_PARAMS
- report_error " Please either use branches or releases but not both."
- fi
- fi
-}
-
-
-# test whether a branch exists in the cgat-core repository
-# https://stackoverflow.com/questions/12199059/how-to-check-if-an-url-exists-with-the-shell-and-probably-curl
-test_core_branch() {
- RELEASE_TEST=0
- curl --output /dev/null --silent --head --fail https://raw.githubusercontent.com/cgat-developers/cgat-core/${CORE_BRANCH}/README.md || RELEASE_TEST=$?
- if [[ ${RELEASE_TEST} -ne 0 ]] ; then
- echo
- echo " The branch provided for cgat-core does not exist: ${CORE_BRANCH}"
- echo
- echo " Please have a look at valid branches here: "
- echo " https://github.com/cgat-developers/cgat-core/branches"
- echo
- report_error " Please use a valid branch and try again."
- fi
-}
-
-
-# test whether a release exists or not
-# https://stackoverflow.com/questions/12199059/how-to-check-if-an-url-exists-with-the-shell-and-probably-curl
-test_release() {
- RELEASE_TEST=0
- curl --output /dev/null --silent --head --fail https://raw.githubusercontent.com/cgat-developers/cgat-apps/${RELEASE}/README.rst || RELEASE_TEST=$?
- if [[ ${RELEASE_TEST} -ne 0 ]] ; then
- echo
- echo " The release number provided does not exist: ${RELEASE}"
- echo
- echo " Please have a look at valid releases here: "
- echo " https://github.com/cgat-developers/cgat-apps/releases"
- echo
- echo " An example of valid release is: --release v0.4.0"
- report_error " Please use a valid release and try again."
- fi
-}
-
-
-# test whether a C/C++ compiler is available
-test_compilers() {
- which gcc &> /dev/null || report_error " C compiler not found "
- which g++ &> /dev/null || report_error " C++ compiler not found "
-}
-
-
-# clean up environment
-# deliberately use brute force
-cleanup_env() {
- set +e
- source deactivate >& /dev/null || true
- source deactivate >& /dev/null || true
- unset -f conda || true
- unset PYTHONPATH || true
- # Next actions disabled. Please see:
- # https://github.com/cgat-developers/cgat-core/issues/44
- #module purge >& /dev/null || true
- #mymodule purge >& /dev/null || true
- set -e
-}
-
-
-# function to display help message
-help_message() {
-echo
-echo " This script uses Conda to install cgat-apps. To proceed, please type:"
-echo " ./install.sh --devel [--location ]"
-echo
-echo " The default install folder will be: $HOME/cgat-install"
-echo
-echo " It will create a new Conda environment ready to run the CGAT code."
-echo
-echo " It is also possible to install/test a specific branch of the code on github:"
-echo " ./install.sh --devel --branch [--location ]"
-echo
-echo " By default, cgat-apps will install the master branch of cgat-core:"
-echo " https://github.com/cgat-developers/cgat-core"
-echo
-echo " Change that with:"
-echo " ./install.sh --devel --core-branch "
-echo
-echo " To test the installation:"
-echo " ./install.sh --test [--location ]"
-echo
-echo " To update the Conda packages:"
-echo " ./install.sh --update [--location ]"
-echo
-echo " To uninstall the CGAT code:"
-echo " ./install.sh --uninstall [--location ]"
-echo
-echo " Please submit any issues via Git Hub:"
-echo " https://github.com/cgat-developers/cgat-apps/issues"
-echo
-exit 1
-} # help_message
-
-# the script starts here
-
-cleanup_env
-test_compilers
-
-if [[ $# -eq 0 ]] ; then
-
- help_message
-
-fi
-
-# travis execution
-TRAVIS_INSTALL=
-# jenkins testing
-JENKINS_INSTALL=
-# conda installation type
-INSTALL_PRODUCTION=
-INSTALL_DEVEL=
-# test current installation
-INSTALL_TEST=
-# update current installation
-INSTALL_UPDATE=
-# uninstall CGAT code
-UNINSTALL=
-UNINSTALL_DIR=
-# where to install CGAT code
-CGAT_HOME=
-# how to download CGAT code:
-# 0 = as zip (default)
-# 1 = git clone with https
-# 2 = git clone with ssh
-CODE_DOWNLOAD_TYPE=0
-# which github branch to use (default: master)
-INSTALL_BRANCH="master"
-# which github branch to use for cgat-core (default: master)
-CORE_BRANCH="master"
-# rename conda environment
-CONDA_INSTALL_ENV=
-# type of installation
-CONDA_INSTALL_TYPE_APPS=
-CONDA_INSTALL_TYPE_CORE=
-# Install a released version?
-RELEASE=
-
-# parse input parameters
-# https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
-# https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
-
-while [[ $# -gt 0 ]]
-do
-key="$1"
-
-case $key in
-
- --help)
- help_message
- ;;
-
- --travis)
- TRAVIS_INSTALL=1
- shift # past argument
- ;;
-
- --jenkins)
- JENKINS_INSTALL=1
- shift # past argument
- ;;
-
- --zip)
- CODE_DOWNLOAD_TYPE=0
- shift
- ;;
-
- --git)
- CODE_DOWNLOAD_TYPE=1
- shift
- test_git
- ;;
-
- --git-ssh)
- CODE_DOWNLOAD_TYPE=2
- shift
- test_git
- test_git_ssh
- ;;
-
- --production)
- INSTALL_PRODUCTION=1
- shift
- ;;
-
- --devel)
- INSTALL_DEVEL=1
- shift
- ;;
-
- --test)
- INSTALL_TEST=1
- shift
- ;;
-
- --update)
- INSTALL_UPDATE=1
- shift
- ;;
-
- --uninstall)
- UNINSTALL=1
- shift
- ;;
-
- --location)
- CGAT_HOME="$2"
- shift 2
- ;;
-
- --branch)
- INSTALL_BRANCH="$2"
- test_mix_branch_release
- shift 2
- ;;
-
- --core-branch)
- CORE_BRANCH="$2"
- test_core_branch
- shift 2
- ;;
-
- --release)
- RELEASE="$2"
- test_mix_branch_release
- test_release
- INSTALL_BRANCH="$2"
- shift 2
- ;;
-
- --env-name)
- CONDA_INSTALL_ENV="$2"
- shift 2
- ;;
-
- *)
- echo
- echo
- echo " Wrong input: ${SCRIPT_NAME} ${SCRIPT_PARAMS}"
- echo
- help_message
- ;;
-
-esac
-done
-
-# sanity check 1: don't mix production and development installs
-if [[ $INSTALL_PRODUCTION ]] && [[ $INSTALL_DEVEL ]] ; then
-
- report_error " Incorrect input arguments: mixing --production and --devel is not permitted. "
-
-fi
-
-# sanity check 2: make sure one installation option is selected
-if [[ -z $INSTALL_TEST ]] && \
- [[ -z $INSTALL_PRODUCTION ]] && \
- [[ -z $INSTALL_DEVEL ]] && \
- [[ -z $TRAVIS_INSTALL ]] && \
- [[ -z $JENKINS_INSTALL ]] ; then
-
- report_error " You need to select either --devel or --production. "
-
-fi
-
-# sanity check 3: make sure there is space available in the destination folder (10 GB) in 512-byte blocks
-[[ -z ${TRAVIS_INSTALL} ]] && \
-mkdir -p ${CGAT_HOME} && \
-[[ `df -P ${CGAT_HOME} | awk '/\// {print $4}'` -lt 20971520 ]] && \
- report_error " Not enough disk space available on the installation folder: "$CGAT_HOME
-
-# perform actions according to the input parameters processed
-if [[ $TRAVIS_INSTALL ]] || [[ $JENKINS_INSTALL ]] ; then
-
- conda_install
- conda_test
-
-else
-
- if [[ $INSTALL_PRODUCTION ]] || [[ $INSTALL_DEVEL ]] ; then
- conda_install
- fi
-
- if [[ $INSTALL_TEST ]] ; then
- conda_test
- fi
-
- if [[ $INSTALL_UPDATE ]] ; then
- conda_update
- fi
-
- if [[ $UNINSTALL ]] ; then
- uninstall
- fi
-
-fi # if-variables
diff --git a/requires.txt b/requires.txt
deleted file mode 100644
index e69de29bb..000000000
diff --git a/setup.py b/setup.py
index adb4ccd73..cd7a2ef0d 100644
--- a/setup.py
+++ b/setup.py
@@ -137,37 +137,6 @@ def is_exe(fpath):
HTTPS_REQUIREMENT = re.compile(
r'^-e (?P.*).+#(?P.+)-(?P\d(?:\.\d)*)$')
install_requires = []
-dependency_links = []
-
-for requirement in (
- l.strip() for l in open('requires.txt') if not l.startswith("#")):
- match = REPO_REQUIREMENT.match(requirement)
- if match:
- assert which(match.group('vcs')) is not None, \
- ("VCS '%(vcs)s' must be installed in order to "
- "install %(link)s" % match.groupdict())
- install_requires.append("%(package)s==%(version)s" % match.groupdict())
- dependency_links.append(match.group('link'))
- continue
-
- if requirement.startswith("https"):
- install_requires.append(requirement)
- continue
-
- match = HTTPS_REQUIREMENT.match(requirement)
- if match:
- install_requires.append("%(package)s>=%(version)s" % match.groupdict())
- dependency_links.append(match.group('link'))
- continue
-
- install_requires.append(requirement)
-
-if major == 2:
- install_requires.extend(['web.py>=0.37',
- 'xlwt>=0.7.4',
- 'matplotlib-venn>=0.5'])
-elif major == 3:
- pass
cgat_packages = find_packages()
cgat_package_dirs = {'cgat': 'cgat'}
@@ -317,7 +286,6 @@ def is_exe(fpath):
},
# dependencies
install_requires=install_requires,
- dependency_links=dependency_links,
# extension modules
ext_modules=extensions,
cmdclass={'build_ext': build_ext},
diff --git a/tests/test_commandline.py b/tests/test_commandline.py
index 68e8d124f..0feb1d953 100644
--- a/tests/test_commandline.py
+++ b/tests/test_commandline.py
@@ -1,5 +1,6 @@
-'''test_commandline - test coding style confirmation of CGAT code
-===========================================================
+'''
+test_commandline - Tests coding style conformity of CGAT code collection.
+==========================================================================
:Author: Andreas Heger
:Release: $Id$
@@ -8,24 +9,18 @@
Purpose
-------
-
-This script test the command line usage of all scripts in the
-CGAT code collection.
-
-This script is best run within nosetests::
+This script tests the command line usage of all scripts in the CGAT code collection.
+It's recommended to run this within nosetests for efficiency and coverage:
nosetests tests/test_commandline.py --nocapture
+Before running these tests, ensure to execute:
-.. note::
-
- Make sure to run::
-
- python setup.py develop
-
- Before running these tests.
+ python setup.py develop
+to make all package scripts available for import and testing.
'''
+
import glob
import os
import importlib
@@ -33,217 +28,160 @@
import re
import sys
import copy
-import platform
+import argparse
from nose.tools import ok_
import cgatcore.experiment as E
import cgatcore.iotools as iotools
import TestUtils
-# handle to original E.Start function
-ORIGINAL_START = None
+# Preserve the original E.Start function for later restoration
+ORIGINAL_START = E.start
-# Parser object collected from child script
+# Placeholders for parser object and tested script's module
PARSER = None
+TESTED_MODULE = None
-# DIRECTORIES to examine for python modules/scripts
+# Directories to examine for Python modules/scripts
EXPRESSIONS = (
- ('tools', 'cgat/tools/*.py'),)
+ ('tools', 'cgat/tools/*.py'),
+)
+# Files to exclude from checks
EXCLUDE = [
"__init__.py",
"version.py",
"cgat.py",
- "gtf2table.py", # fails with pysam include issue
- "bed2table.py", # fails with pysam include issue
- "fasta2bed.py", # fails because of pybedtools rebuild
+ "gtf2table.py", # Fails with pysam include issue
+ "bed2table.py", # Fails with pysam include issue
+ "fasta2bed.py", # Fails due to pybedtools rebuild requirements
]
-# Filename with the black/white list of options.
-# The file is a tab-separated with the first column
-# an option name and the second field a marker.
-# Possible markers are:
-# ok = whitelist - this option is ok.
-# 'bad', 'rename', '?', '' - this option is not ok.
+# Filename for the black/white list of options
FILENAME_OPTIONLIST = "tests/option_list.tsv"
class DummyError(Exception):
+ """Custom exception for controlling test flow."""
pass
def filter_files(files):
- '''filter list of files according to filters set in
- configuration file tests/_test_commandline.yml'''
-
- # directory location of tests
+ '''Filter list of files according to filters set in the configuration file tests/_test_commandline.yml'''
testing_dir = TestUtils.get_tests_directory()
-
- # the config file
config_file = os.path.join(testing_dir, "_test_commandline.yml")
if os.path.exists(config_file):
- config = yaml.safe_load(open(config_file))
- if config is not None:
- if "restrict" in config and config["restrict"]:
+ with open(config_file) as cf:
+ config = yaml.safe_load(cf)
+ if config and "restrict" in config:
values = config["restrict"]
if "manifest" in values:
- # take scripts defined in the MANIFEST.in file
- scriptdirs = [x for x in open("MANIFEST.in")
- if x.startswith("include CGAT/tools") and
- x.endswith(".py\n")]
-
- take = set([re.sub("include\s*", "",
- x[:-1]) for x in scriptdirs])
+ scriptdirs = [x.strip() for x in open("MANIFEST.in")
+ if x.startswith("include CGAT/tools") and x.endswith(".py\n")]
+ take = set(re.sub("include\s*", "", x) for x in scriptdirs)
files = [x for x in files if x in take]
-
if "regex" in values:
rx = re.compile(values["regex"])
- files = filter(rx.search, files)
+ files = list(filter(rx.search, files))
return files
def LocalStart(parser, *args, **kwargs):
- '''stub for E.start - set return_parser argument to true'''
+ '''Stub for E.start - captures the parser for inspection.'''
global PARSER
- d = copy.copy(kwargs)
- d.update({'return_parser': True})
- PARSER = ORIGINAL_START(parser, **d)
+ kwargs.update({'return_parser': True})
+ PARSER = ORIGINAL_START(parser, **kwargs)
raise DummyError()
def load_script(script_name):
+ '''Attempts to import a script as a module for inspection.'''
+ script_path = os.path.splitext(script_name)[0]
+ script_dir, script_base = os.path.split(script_path)
+ module_name = ".".join(filter(None, [script_dir.replace(os.sep, '.'), script_base]))
- # call other script
- prefix, suffix = os.path.splitext(script_name)
-
- dirname = os.path.relpath(os.path.dirname(script_name))
- basename = os.path.basename(script_name)[:-3]
+ # Remove compiled files to ensure fresh import
+ compiled_script = script_path + ".pyc"
+ if os.path.exists(compiled_script):
+ os.remove(compiled_script)
- if os.path.exists(prefix + ".pyc"):
- try:
- os.remove(prefix + ".pyc")
- except OSError:
- pass
-
- modulename = ".".join((re.sub("/", ".", dirname), basename))
try:
- module = importlib.import_module(modulename)
- except ImportError as msg:
- sys.stderr.write('could not import %s - skipped: %s\n' %
- (modulename, msg))
- module = None
-
- return module, modulename
+ module = importlib.import_module(module_name)
+ except ImportError as e:
+ sys.stderr.write(f'ImportError for {module_name}: {e}\n')
+ return None, None
-
-def check_option(option, script_name, map_option2action):
- '''import script and get command line options.
-
- Test command line options for conformity.
- '''
- if option in map_option2action:
- ok_(option in map_option2action,
- 'option %s:%s unknown')
- ok_(map_option2action[option] == "ok",
- 'option %s:%s wrong: action="%s"' %
- (script_name, option, map_option2action[option]))
-
-
-def fail_(msg):
- '''create test that fails with *msg*.'''
- ok_(False, msg)
+ return module, module_name
def test_cmdline():
- '''test style of scripts
- '''
-
- # start script in order to build the command line parser
+ '''Test command line interfaces of scripts for style and conformity.'''
global ORIGINAL_START
- if ORIGINAL_START is None:
- ORIGINAL_START = E.start
- # read the first two columns
- map_option2action = iotools.read_map(
+
+ # Load option actions from list
+ option_actions = iotools.read_map(
iotools.open_file(FILENAME_OPTIONLIST),
columns=(0, 1),
- has_header=True)
-
- files = []
- for label, expression in EXPRESSIONS:
- f = glob.glob(expression)
- files.extend(sorted(f))
+ has_header=True
+ )
+ # Compile list of scripts to test
+ files = [f for label, expr in EXPRESSIONS for f in glob.glob(expr)]
files = filter_files(files)
- # make sure to use the current working directory as
- # primary lookup.
+ # Prioritise the current directory for module lookup
sys.path.insert(0, ".")
- # files = [
- # 'scripts/check_db.py',
- # 'scripts/cgat_build_report_page.py']
-
- for f in files:
- if os.path.isdir(f):
- continue
- if os.path.basename(f) in EXCLUDE:
+ for script in files:
+ if os.path.isdir(script) or os.path.basename(script) in EXCLUDE:
continue
- script_name = os.path.abspath(f)
- pyxfile = (os.path.join(os.path.dirname(f), "_") +
- os.path.basename(f) + "x")
-
- fail_.description = script_name
- # check if script contains getopt
- with iotools.open_file(script_name) as inf:
- if "getopt" in inf.read():
- yield (fail_,
- "script uses getopt directly: %s" % script_name)
- continue
-
- module, modulename = load_script(script_name)
- if module is None:
- yield (fail_,
- "module could not be imported: %s\n" % script_name)
+ script_name = os.path.abspath(script)
+ module, module_name = load_script(script)
+ if not module:
+ yield fail_, f"Module {script_name} could not be imported."
continue
+
+ # Replace the start function to capture parser
E.start = LocalStart
+ # Attempt to run script's main function to access its parser
try:
- module.main(argv=["dummy", "--help"])
- except AttributeError:
- yield (fail_,
- "no main method in %s\n" % script_name)
- ok_(False, "no main method in %s" % script_name)
- except SystemExit:
- yield (fail_,
- "script does not use E.start() %s\n" % script_name)
+ module.main(argv=["--help"])
except DummyError:
+ # Expected flow interruption by LocalStart
pass
+ except Exception as e:
+ yield fail_, f"Error invoking main of {script_name}: {e}"
+ continue
+
+ if PARSER:
+ for action in PARSER._actions: # Iterate through the actions stored in the parser
+ if isinstance(action, argparse._HelpAction): # Skip help actions
+ continue
+ opt_strings = action.option_strings # Get the list of CLI flags
+ if not opt_strings: # This skips positional arguments
+ continue
+ for opt_string in opt_strings:
+ if opt_string.startswith("--"):
+ opt_string = opt_string[2:]
+ yield check_option, opt_string, script_name, option_actions
- for option in PARSER.parse_args(parser):
- print("=================")
- print(option)
- # ignore options added by optparse
- if option.dest is None:
- continue
+ # Reset module to avoid conflicts
+ if module_name in sys.modules:
+ del sys.modules[module_name]
- optstring = option.get_opt_string()
- if optstring.startswith("--"):
- optstring = optstring[2:]
- check_option.description = script_name + ":" + optstring
+def check_option(option, script_name, option_actions):
+ print(f"Checking option: {option} in script: {script_name}") # Diagnostic print
+ assert option in option_actions, f"Option {option} in script {script_name} is unknown or not allowed."
+ assert option_actions[option] == "ok", f"Option {option} in script {script_name} is not allowed."
- yield(check_option, optstring, os.path.abspath(f),
- map_option2action)
- # clear up
- del sys.modules[modulename]
+def fail_(msg):
+ '''Generate a failing test with the provided message.'''
+ ok_(False, msg)
- # scripts with pyximport need special handling.
- #
- # Multiple imports of pyximport seems to create
- # some confusion - here, clear up sys.meta_path after
- # each script
- if os.path.exists(pyxfile):
- sys.meta_path = []
+# Reset E.start to its original function after testing
+E.start = ORIGINAL_START