diff --git a/.coveragerc b/.coveragerc index 24ed094..fa03bbf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,2 @@ [run] -source = - KiBOM_CLI.py - ./bomlib \ No newline at end of file +source = ./kibom \ No newline at end of file diff --git a/.gitignore b/.gitignore index 30e5569..a8b35fe 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,9 @@ venv.bak/ .coverage htmlcov/ +# PIP build +build/ +dist/ +kibom.egg-info/ + .python-version diff --git a/.travis.yml b/.travis.yml index b9e4929..9d3f63d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,23 +6,27 @@ language: python python: - 2.7 - - 3.6 + - 3.7 install: - pip install -r test/requirements.txt + - pip install wheel addons: apt: update: true before_install: - - sudo apt-get install python3-pip python3-coverage python3-xlsxwriter + - pip install coverage + - pip install xlsxwriter script: # Check Python code for style-guide - flake8 . # Run the coverage tests - bash ./run-tests.sh + # Ensure the module can actually build + - python setup.py bdist_wheel --universal after_success: - coveralls \ No newline at end of file diff --git a/KiBOM_CLI.py b/KiBOM_CLI.py deleted file mode 100755 index 51628b7..0000000 --- a/KiBOM_CLI.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" - @package - KiBOM - Bill of Materials generation for KiCad - - Generate BOM in xml, csv, txt, tsv, html or xlsx formats. - - - Components are automatically grouped into BoM rows (grouping is configurable) - - Component groups count number of components and list component designators - - Rows are automatically sorted by component reference(s) - - Supports board variants - - Extended options are available in the "bom.ini" config file in the PCB directory (this file is auto-generated with default options the first time the script is executed). - -""" - -from __future__ import print_function - -import sys -import os - -import argparse - -from bomlib.columns import ColumnList -from bomlib.netlist_reader import netlist -from bomlib.bom_writer import WriteBoM -from bomlib.preferences import BomPref - -try: - import xlsxwriter # noqa: F401 -except: - xlsxwriter_available = False -else: - xlsxwriter_available = True - -KIBOM_VERSION = "1.6.1" - -here = os.path.abspath(os.path.dirname(sys.argv[0])) - -sys.path.append(here) -sys.path.append(os.path.join(here, "KiBOM")) - -verbose = False - - -def close(*arg): - print(*arg) - sys.exit(0) - - -def say(*arg): - # Simple debug message handler - if verbose: - print(*arg) - - -def isExtensionSupported(filename): - result = False - extensions = [".xml", ".csv", ".txt", ".tsv", ".html"] - if xlsxwriter_available: - extensions.append(".xlsx") - for e in extensions: - if filename.endswith(e): - result = True - break - return result - - -def writeVariant(variant, subdirectory): - if variant is not None: - pref.pcbConfig = variant.strip().split(',') - - print("PCB variant: ", ", ".join(pref.pcbConfig)) - - # Write preference file back out (first run will generate a file with default preferences) - if not have_cfile: - pref.Write(config_file) - say("Writing preferences file %s" % (config_file,)) - - # Individual components - components = [] - - # Component groups - groups = [] - - # Read out the netlist - net = netlist(input_file, prefs=pref) - - # Extract the components - components = net.getInterestingComponents() - - # Group the components - groups = net.groupComponents(components) - - columns = ColumnList(pref.corder) - - # Read out all available fields - for g in groups: - for f in g.fields: - columns.AddColumn(f) - - # Don't add 'boards' column if only one board is specified - if pref.boards <= 1: - columns.RemoveColumn(ColumnList.COL_GRP_BUILD_QUANTITY) - say("Removing:", ColumnList.COL_GRP_BUILD_QUANTITY) - - # Finally, write the BoM out to file - if write_to_bom: - output_file = args.output - - if output_file is None: - output_file = input_file.replace(".xml", ".csv") - - output_path, output_name = os.path.split(output_file) - output_name, output_ext = os.path.splitext(output_name) - - # KiCad BOM dialog by default passes "%O" without an extension. Append our default - if not isExtensionSupported(output_ext): - output_ext = ".csv" - - # Make replacements to custom file_name. - file_name = pref.outputFileName - - file_name = file_name.replace("%O", output_name) - file_name = file_name.replace("%v", net.getVersion()) - if variant is not None: - file_name = file_name.replace("%V", pref.variantFileNameFormat) - file_name = file_name.replace("%V", variant) - else: - file_name = file_name.replace("%V", "") - - if args.subdirectory is not None: - output_path = os.path.join(output_path, args.subdirectory) - if not os.path.exists(os.path.abspath(output_path)): - os.makedirs(os.path.abspath(output_path)) - - output_file = os.path.join(output_path, file_name + output_ext) - output_file = os.path.abspath(output_file) - - say("Output:", output_file) - - return WriteBoM(output_file, groups, net, columns.columns, pref) - - -parser = argparse.ArgumentParser(description="KiBOM Bill of Materials generator script") - -parser.add_argument("netlist", help='xml netlist file. Use "%%I" when running from within KiCad') -parser.add_argument("output", default="", help='BoM output file name.\nUse "%%O" when running from within KiCad to use the default output name (csv file).\nFor e.g. HTML output, use "%%O.html"') -parser.add_argument("-n", "--number", help="Number of boards to build (default = 1)", type=int, default=None) -parser.add_argument("-v", "--verbose", help="Enable verbose output", action='count') -parser.add_argument("-r", "--variant", help="Board variant(s), used to determine which components are output to the BoM. To specify multiple variants, with a BOM file exported for each variant, separate variants with the ';' (semicolon) character.", type=str, default=None) -parser.add_argument("-d", "--subdirectory", help="Subdirectory within which to store the generated BoM files.", type=str, default=None) -parser.add_argument("--cfg", help="BoM config file (script will try to use 'bom.ini' if not specified here)") -parser.add_argument("-s", "--separator", help="CSV Separator (default ',')", type=str, default=None) -parser.add_argument('--version', action='version', version="KiBom Version: {v}".format(v=KIBOM_VERSION)) - -args = parser.parse_args() - -input_file = args.netlist - -if not input_file.endswith(".xml"): - close("{i} is not a valid xml file".format(i=input_file)) - -verbose = args.verbose is not None - -input_file = os.path.abspath(input_file) - -say("Input:", input_file) - -# Look for a config file! -# bom.ini by default -ini = os.path.abspath(os.path.join(os.path.dirname(input_file), "bom.ini")) - -# Default value -config_file = ini - -# User can overwrite with a specific config file -if args.cfg: - config_file = args.cfg - -# Read preferences from file. If file does not exists, default preferences will be used -pref = BomPref() - -have_cfile = os.path.exists(config_file) -if have_cfile: - pref.Read(config_file) - say("Config:", config_file) - -# Pass available modules -pref.xlsxwriter_available = xlsxwriter_available - -# Pass various command-line options through -pref.verbose = verbose -if args.number is not None: - pref.boards = args.number -pref.separatorCSV = args.separator - -write_to_bom = True - -if args.variant is not None: - variants = args.variant.split(';') -else: - variants = [None] - -for variant in variants: - result = writeVariant(variant, args) - if not result: - sys.exit(-1) - -sys.exit(0) diff --git a/README.md b/README.md index 0d65855..1d39230 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,42 @@ KiBoM intelligently groups components based on multiple factors, and can generat BoM options are user-configurable in a per-project configuration file. +## Installation + +KiBom can be installed through the PIP package manager: + +```pip install kibom``` + +*Note: Take note of which python executable you use when installing kibom - this is the same executable you must use when running the KiBom script from KiCAD (more details below under "Usage")* + ## Usage -The *KiBOM_CLI* script can be run directly from KiCad or from the command line. For command help, run the script with the *-h* flag e.g. +The *KiBOM_CLI* script can be run directly from KiCad or from the command line, e.g. + +`python -m kibom "%I" "%O.csv"` + +**Note: Selecting python executable** + +The python executable you choose (i.e. the *python* part of the command above) **must** be the same as the one you used to install kibom (using pip). + +By default KiCad uses the version of python packaged with the KiCad application (i.e. kicad/bin/python). + +**Example: Install kibom under python3 on windows** -`python KiBOM_CLI.py -h` +You have installed kibom using Python3: `pip3 install kibom` + +To launch the script correctly from the KiCad BOM window, use the command: + +`python3.exe -m kibom "%I" "%O.csv"` + +If you are running a different python version, select that accordingly. + +For command help, run the script with the *-h* flag e.g. + +`python -m kibom -h` ~~~~ -usage: KiBOM_CLI.py [-h] [-n NUMBER] [-v] [-r VARIANT] [-d SUBDIRECTORY] +usage: python -m kibom [-h] [-n NUMBER] [-v] [-r VARIANT] [-d SUBDIRECTORY] [--cfg CFG] [-s SEPARATOR] [--version] netlist output @@ -364,17 +392,6 @@ C3 and C5 have the same value and footprint **C4** C4 has a different footprint to C3 and C5, and thus is grouped separately -A HTML BoM file is generated as follows: - -![alt tag](example/bom.png?raw=True "BoM") - -To add the BoM script, the Command Line options should be configured as follows: -* path-to-python-script (KiBOM_CLI.py) -* netlist-file "%I" -* output_path "%O_bom.html" (replace file extension for different output file formats) - -Hit the "Generate" button, and the output window should show that the BoM generation was successful. - ### HTML Output The output HTML file is generated as follows: diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bomlib/__init__.py b/bomlib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/example/bom.png b/example/bom.png deleted file mode 100644 index 775221c..0000000 Binary files a/example/bom.png and /dev/null differ diff --git a/example/html_ex.png b/example/html_ex.png index 035b814..521ee00 100644 Binary files a/example/html_ex.png and b/example/html_ex.png differ diff --git a/kibom/__init__.py b/kibom/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/kibom/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/kibom/__main__.py b/kibom/__main__.py new file mode 100644 index 0000000..938b32e --- /dev/null +++ b/kibom/__main__.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +""" + @package + KiBOM - Bill of Materials generation for KiCad + + Generate BOM in xml, csv, txt, tsv, html or xlsx formats. + + - Components are automatically grouped into BoM rows (grouping is configurable) + - Component groups count number of components and list component designators + - Rows are automatically sorted by component reference(s) + - Supports board variants + + Extended options are available in the "bom.ini" config file in the PCB directory (this file is auto-generated with default options the first time the script is executed). + + For usage help: + python -m kibom -h +""" + +from __future__ import print_function + +import sys +import os +import argparse + +from .columns import ColumnList +from .netlist_reader import netlist +from .bom_writer import WriteBoM +from .preferences import BomPref +from .version import KIBOM_VERSION +from . import debug + + +def writeVariant(input_file, output_file, variant, subdirectory, pref): + if variant is not None: + pref.pcbConfig = variant.strip().split(',') + + debug.message("PCB variant:", ", ".join(pref.pcbConfig)) + + # Individual components + components = [] + + # Component groups + groups = [] + + # Read out the netlist + net = netlist(input_file, prefs=pref) + + # Extract the components + components = net.getInterestingComponents() + + # Group the components + groups = net.groupComponents(components) + + columns = ColumnList(pref.corder) + + # Read out all available fields + for g in groups: + for f in g.fields: + columns.AddColumn(f) + + # Don't add 'boards' column if only one board is specified + if pref.boards <= 1: + columns.RemoveColumn(ColumnList.COL_GRP_BUILD_QUANTITY) + debug.info("Removing:", ColumnList.COL_GRP_BUILD_QUANTITY) + + if output_file is None: + output_file = input_file.replace(".xml", ".csv") + + output_path, output_name = os.path.split(output_file) + output_name, output_ext = os.path.splitext(output_name) + + # KiCad BOM dialog by default passes "%O" without an extension. Append our default + if output_ext == "": + output_ext = ".csv" + debug.info("No extension supplied for output file - using .csv") + elif output_ext not in [".xml", ".csv", ".txt", ".tsv", ".html", ".xlsx"]: + output_ext = ".csv" + debug.warning("Unknown extension '{e}' supplied - using .csv".format(e=output_ext)) + + # Make replacements to custom file_name. + file_name = pref.outputFileName + + file_name = file_name.replace("%O", output_name) + file_name = file_name.replace("%v", net.getVersion()) + if variant is not None: + file_name = file_name.replace("%V", pref.variantFileNameFormat) + file_name = file_name.replace("%V", variant) + else: + file_name = file_name.replace("%V", "") + + output_file = os.path.join(output_path, file_name + output_ext) + output_file = os.path.abspath(output_file) + + debug.message("Output:", output_file) + + return WriteBoM(output_file, groups, net, columns.columns, pref) + + +def main(): + + parser = argparse.ArgumentParser(prog="python -m kibom", description="KiBOM Bill of Materials generator script") + + parser.add_argument("netlist", help='xml netlist file. Use "%%I" when running from within KiCad') + parser.add_argument("output", default="", help='BoM output file name.\nUse "%%O" when running from within KiCad to use the default output name (csv file).\nFor e.g. HTML output, use "%%O.html"') + parser.add_argument("-n", "--number", help="Number of boards to build (default = 1)", type=int, default=None) + parser.add_argument("-v", "--verbose", help="Enable verbose output", action='count') + parser.add_argument("-r", "--variant", help="Board variant(s), used to determine which components are output to the BoM. To specify multiple variants, with a BOM file exported for each variant, separate variants with the ';' (semicolon) character.", type=str, default=None) + parser.add_argument("-d", "--subdirectory", help="Subdirectory within which to store the generated BoM files.", type=str, default=None) + parser.add_argument("--cfg", help="BoM config file (script will try to use 'bom.ini' if not specified here)") + parser.add_argument("-s", "--separator", help="CSV Separator (default ',')", type=str, default=None) + parser.add_argument('--version', action='version', version="KiBom Version: {v}".format(v=KIBOM_VERSION)) + + args = parser.parse_args() + + # Set the global debugging level + debug.setDebugLevel(int(args.verbose) if args.verbose is not None else debug.MSG_ERROR) + + input_file = os.path.abspath(args.netlist) + + output_file = args.output + + if args.subdirectory is not None: + output_file = os.path.join(args.subdirectory, output_file) + + # Make the directory if it does not exist + if not os.path.exists(os.path.abspath(output_file)): + os.makedirs(os.path.abspath(output_file)) + + if not input_file.endswith(".xml"): + debug.error("Input file '{f}' is not an xml file".format(f=input_file), fail=True) + + if not os.path.exists(input_file) or not os.path.isfile(input_file): + debug.error("Input file '{f}' does not exist".format(f=input_file), fail=True) + + debug.message("Input:", input_file) + + # Look for a config file! + # bom.ini by default + ini = os.path.abspath(os.path.join(os.path.dirname(input_file), "bom.ini")) + + # Default value + config_file = ini + + # User can overwrite with a specific config file + if args.cfg: + config_file = args.cfg + + # Read preferences from file. If file does not exists, default preferences will be used + pref = BomPref() + + have_cfile = os.path.exists(config_file) + + if have_cfile: + pref.Read(config_file) + debug.message("Configuration file:", config_file) + else: + pref.Write(config_file) + debug.info("Writing configuration file:", config_file) + + # Pass various command-line options through + if args.number is not None: + pref.boards = args.number + + pref.separatorCSV = args.separator + + if args.variant is not None: + variants = args.variant.split(';') + else: + variants = [None] + + # Generate BOMs for each specified variant + for variant in variants: + result = writeVariant(input_file, output_file, variant, args, pref) + if not result: + sys.exit(-1) + + sys.exit(debug.getErrorCount()) + + +if __name__ == '__main__': + main() diff --git a/bomlib/bom_writer.py b/kibom/bom_writer.py similarity index 68% rename from bomlib/bom_writer.py rename to kibom/bom_writer.py index 15d5df5..4b04e14 100644 --- a/bomlib/bom_writer.py +++ b/kibom/bom_writer.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- -from bomlib.csv_writer import WriteCSV -from bomlib.xml_writer import WriteXML -from bomlib.html_writer import WriteHTML -from bomlib.xlsx_writer import WriteXLSX +from .csv_writer import WriteCSV +from .xml_writer import WriteXML +from .html_writer import WriteHTML +from .xlsx_writer import WriteXLSX -import bomlib.columns as columns -from bomlib.preferences import BomPref +from . import columns +from . import debug +from .preferences import BomPref import os import shutil @@ -54,33 +55,33 @@ def WriteBoM(filename, groups, net, headings=columns.ColumnList._COLUMNS_DEFAULT # CSV file writing if ext in ["csv", "tsv", "txt"]: if WriteCSV(filename, groups, net, headings, prefs): - print("CSV Output -> {fn}".format(fn=filename)) + debug.info("CSV Output -> {fn}".format(fn=filename)) result = True else: - print("Error writing CSV output") + debug.error("Error writing CSV output") elif ext in ["htm", "html"]: if WriteHTML(filename, groups, net, headings, prefs): - print("HTML Output -> {fn}".format(fn=filename)) + debug.info("HTML Output -> {fn}".format(fn=filename)) result = True else: - print("Error writing HTML output") + debug.error("Error writing HTML output") elif ext in ["xml"]: if WriteXML(filename, groups, net, headings, prefs): - print("XML Output -> {fn}".format(fn=filename)) + debug.info("XML Output -> {fn}".format(fn=filename)) result = True else: - print("Error writing XML output") + debug.error("Error writing XML output") - elif ext in ["xlsx"] and prefs.xlsxwriter_available: + elif ext in ["xlsx"]: if WriteXLSX(filename, groups, net, headings, prefs): - print("XLSX Output -> {fn}".format(fn=filename)) + debug.info("XLSX Output -> {fn}".format(fn=filename)) result = True else: - print("Error writing XLSX output") + debug.error("Error writing XLSX output") else: - print("Unsupported file extension: {ext}".format(ext=ext)) + debug.error("Unsupported file extension: {ext}".format(ext=ext)) return result diff --git a/bomlib/columns.py b/kibom/columns.py similarity index 100% rename from bomlib/columns.py rename to kibom/columns.py diff --git a/bomlib/component.py b/kibom/component.py similarity index 96% rename from bomlib/component.py rename to kibom/component.py index c9994aa..2eef873 100644 --- a/bomlib/component.py +++ b/kibom/component.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from bomlib.columns import ColumnList -from bomlib.preferences import BomPref -import bomlib.units as units -from bomlib.sort import natural_sort import re import sys +from .columns import ColumnList +from .preferences import BomPref +from . import units +from . import debug +from .sort import natural_sort + # String matches for marking a component as "do not fit" DNF = [ "dnf", @@ -372,12 +374,12 @@ def testRegExclude(self): pass if re.search(regex, field_value, flags=re.IGNORECASE) is not None: - if self.prefs.verbose: - print("Excluding '{ref}': Field '{field}' ({value}) matched '{reg}'".format( - ref=self.getRef(), - field=field_name, - value=field_value, - reg=regex).encode('utf-8')) + debug.info("Excluding '{ref}': Field '{field}' ({value}) matched '{reg}'".format( + ref=self.getRef(), + field=field_name, + value=field_value, + reg=regex).encode('utf-8') + ) # Found a match return True @@ -396,11 +398,9 @@ def testRegInclude(self): field_name, regex = reg field_value = self.getField(field_name) - print(field_name, field_value, regex) + debug.info(field_name, field_value, regex) if re.search(regex, field_value, flags=re.IGNORECASE) is not None: - if self.prefs.verbose: - print("") # Found a match return True @@ -566,7 +566,7 @@ def updateField(self, field, fieldData): elif fieldData.lower() in self.fields[field].lower(): return else: - print("Field conflict: ({refs}) [{name}] : '{flds}' <- '{fld}'".format( + debug.warning("Field conflict: ({refs}) [{name}] : '{flds}' <- '{fld}'".format( refs=self.getRefs(), name=field, flds=self.fields[field], diff --git a/bomlib/csv_writer.py b/kibom/csv_writer.py similarity index 100% rename from bomlib/csv_writer.py rename to kibom/csv_writer.py diff --git a/kibom/debug.py b/kibom/debug.py new file mode 100644 index 0000000..6769787 --- /dev/null +++ b/kibom/debug.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +from __future__ import print_function + +import sys + +# Various msg levels +MSG_MESSAGE = -1 # Display generic message (always displayed) +MSG_ERROR = 0 # Display error messages +MSG_WARN = 1 # Display warning messages +MSG_INFO = 2 # Display information messages +MSG_DEBUG = 3 # Display debug messages + +MSG_CODES = { + MSG_ERROR: "ERROR", + MSG_WARN: "WARNING", + MSG_INFO: "INFO", + MSG_DEBUG: "DEBUG", +} + +# By default, only display error messages +MSG_LEVEL = MSG_ERROR + +# Keep track of accumulated errorsh +ERR_COUNT = 0 + + +def setDebugLevel(level): + global MSG_LEVEL + MSG_LEVEL = int(level) + + +def getErrorCount(): + global ERR_COUNT + return ERR_COUNT + + +def _msg(color, prefix, *arg): + """ + Display a message with the given color. + """ + + msg = color + + if prefix: + msg += prefix + + print(msg, *arg) + + +def message(*arg): + """ + Display a message + """ + + _msg(*arg) + + +def debug(*arg): + """ + Display a debug message. + """ + + global MSG_LEVEL + if MSG_LEVEL < MSG_DEBUG: + return + + _msg(MSG_CODES[MSG_DEBUG], *arg) + + +def info(*arg): + """ + Display an info message. + """ + + global MSG_LEVEL + if MSG_LEVEL < MSG_INFO: + return + + _msg(MSG_CODES[MSG_INFO], *arg) + + +def warning(*arg): + """ + Display a warning message + """ + + global MSG_LEVEL + if MSG_LEVEL < MSG_WARN: + return + + _msg(MSG_CODES[MSG_WARN], *arg) + + +def error(*arg, **kwargs): + """ + Display an error message + """ + + global MSG_LEVEL + global ERR_COUNT + + if MSG_LEVEL < MSG_ERROR: + return + + _msg(MSG_CODES[MSG_ERROR], *arg) + + ERR_COUNT += 1 + + fail = kwargs.get('fail', False) + + if fail: + sys.exit(ERR_COUNT) diff --git a/bomlib/html_writer.py b/kibom/html_writer.py similarity index 98% rename from bomlib/html_writer.py rename to kibom/html_writer.py index eeeb1e9..96500e9 100644 --- a/bomlib/html_writer.py +++ b/kibom/html_writer.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from bomlib.component import ColumnList +from .component import ColumnList +from . import debug BG_GEN = "#E6FFEE" BG_KICAD = "#FFE6B3" @@ -42,7 +43,7 @@ def WriteHTML(filename, groups, net, headings, prefs): """ if not filename.endswith(".html") and not filename.endswith(".htm"): - print("{fn} is not a valid html file".format(fn=filename)) + debug.error("{fn} is not a valid html file".format(fn=filename)) return False nGroups = len(groups) diff --git a/bomlib/netlist_reader.py b/kibom/netlist_reader.py similarity index 97% rename from bomlib/netlist_reader.py rename to kibom/netlist_reader.py index 2e56f35..67bc5a7 100644 --- a/bomlib/netlist_reader.py +++ b/kibom/netlist_reader.py @@ -13,11 +13,9 @@ import os.path import xml.sax as sax -from bomlib.component import (Component, ComponentGroup) - -from bomlib.preferences import BomPref - -# -------------------------------------------------------------------- +from .component import Component, ComponentGroup +from .preferences import BomPref +from . import debug class xmlElement(): @@ -260,7 +258,7 @@ def endDocument(self): break if not c.getLibPart(): - print('missing libpart for ref:', c.getRef(), c.getPartName(), c.getLibName()) + debug.warning('missing libpart for ref:', c.getRef(), c.getPartName(), c.getLibName()) def aliasMatch(self, partName, aliasList): for alias in aliasList: @@ -376,7 +374,7 @@ def load(self, fname): self._reader.setContentHandler(_gNetReader(self)) self._reader.parse(fname) except IOError as e: - print(__file__, ":", e, file=sys.stderr) + debug.error(__file__, ":", e) sys.exit(-1) diff --git a/bomlib/preferences.py b/kibom/preferences.py similarity index 98% rename from bomlib/preferences.py rename to kibom/preferences.py index 63cd239..69deabb 100644 --- a/bomlib/preferences.py +++ b/kibom/preferences.py @@ -5,7 +5,8 @@ import re import os -from bomlib.columns import ColumnList +from .columns import ColumnList +from . import debug # Check python version to determine which version of ConfirParser to import if sys.version_info.major >= 3: @@ -62,7 +63,6 @@ def __init__(self): self.mergeBlankFields = True # Blanks fields will be merged when possible self.hideHeaders = False self.hidePcbInfo = False - self.verbose = False # By default, is not verbose self.configField = "Config" # Default field used for part fitting config self.pcbConfig = ["default"] @@ -72,9 +72,6 @@ def __init__(self): self.outputFileName = "%O_bom_%v%V" self.variantFileNameFormat = "_(%V)" - self.xlsxwriter_available = False - self.xlsxwriter2_available = False - # Default fields used to group components self.groups = [ ColumnList.COL_PART, @@ -125,7 +122,7 @@ def checkInt(self, parser, opt, default=False): def Read(self, file, verbose=False): file = os.path.abspath(file) if not os.path.exists(file) or not os.path.isfile(file): - print("{f} is not a valid file!".format(f=file)) + debug.error("{f} is not a valid file!".format(f=file)) return cf = ConfigParser.RawConfigParser(allow_no_value=True) diff --git a/bomlib/sort.py b/kibom/sort.py similarity index 100% rename from bomlib/sort.py rename to kibom/sort.py diff --git a/bomlib/units.py b/kibom/units.py similarity index 100% rename from bomlib/units.py rename to kibom/units.py diff --git a/kibom/version.py b/kibom/version.py new file mode 100644 index 0000000..505b676 --- /dev/null +++ b/kibom/version.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +KIBOM_VERSION = "1.7.0" diff --git a/bomlib/xlsx_writer.py b/kibom/xlsx_writer.py similarity index 100% rename from bomlib/xlsx_writer.py rename to kibom/xlsx_writer.py diff --git a/bomlib/xml_writer.py b/kibom/xml_writer.py similarity index 100% rename from bomlib/xml_writer.py rename to kibom/xml_writer.py diff --git a/run-tests.sh b/run-tests.sh index 268023e..314cc23 100644 --- a/run-tests.sh +++ b/run-tests.sh @@ -6,16 +6,16 @@ rm test/bom.ini coverage erase # Run a simple test -coverage run -a KiBOM_CLI.py test/kibom-test.xml test/bom-out.csv +coverage run -a -m kibom test/kibom-test.xml test/bom-out.csv # Generate a html file -coverage run -a KiBOM_CLI.py test/kibom-test.xml test/bom-out.html +coverage run -a -m kibom test/kibom-test.xml test/bom-out.html # Generate an XML file -coverage run -a KiBOM_CLI.py test/kibom-test.xml test/bom-out.xml +coverage run -a -m kibom test/kibom-test.xml test/bom-out.xml # Generate an XLSX file -coverage run -a KiBOM_CLI.py test/kibom-test.xml test/bom-out.xlsx +coverage run -a -m kibom test/kibom-test.xml test/bom-out.xlsx # Run the sanity checker on the output BOM files coverage run -a test/test_bom.py diff --git a/setup.cfg b/setup.cfg index 61a1b69..f8bd452 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,5 +6,5 @@ ignore = E501, E722, # - C901 - function is too complex C901, -exclude = .git,__pycache__,*/migrations/* +exclude = .git,__pycache__,*/migrations/*,./build, max-complexity = 20 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f82f400 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +import setuptools + +from kibom.version import KIBOM_VERSION + +long_description = "KiBoM is a configurable BOM (Bill of Materials) generation tool for KiCad EDA. Written in Python, it can be used directly with KiCad software without the need for any external libraries or plugins. KiBoM intelligently groups components based on multiple factors, and can generate BoM files in multiple output formats. For futher information see the KiBom project page" + + +setuptools.setup( + name="kibom", + + version=KIBOM_VERSION, + + author="Oliver Walters", + + author_email="oliver.henry.walters@gmail.com", + + description="Bill of Materials generation tool for KiCad EDA", + + long_description=long_description, + + keywords="kicad, bom, electronics, schematic, bill of materials", + + url="https://github.com/SchrodingersGat/KiBom", + + license="MIT", + + packages=setuptools.find_packages(), + + install_requires=[ + "xlsxwriter", + ], + + python_requires=">=2.7" +)