Macropy adaptation

Mcpy doesn't support compiled Python. So I decided to try Macropy, this is a
test.
Is much harder to get it working:
- The script must be a 2 liner, you can't activate it in any other way.
- The hook breaks the _import function in kiplot.py figuring out how to solve
  it was a real challenge. It failed miserably passing the class instead of
  the ast._ClassDef to the macro.
When I finally got it working I found the code supports compiled Python, but
runs almost 3 times slower.
I keep this in a branch in case I want to revisit it, but doesn't look useful.
This commit is contained in:
Salvador E. Tropea 2020-07-11 19:22:54 -03:00
parent a23477d8c2
commit ef24d19ef2
25 changed files with 182 additions and 133 deletions

View File

@ -0,0 +1,13 @@
from macros import macros, test_macro # noqa: F401
@test_macro
class d(object):
def __init__(self):
super().__init__()
print('d constructor')
self.a = 1
print('Test for decorator')
a = d()

View File

@ -0,0 +1,4 @@
class BaseClass(object):
def __init__(self):
super().__init__()
print('BaseClass constructor')

View File

@ -0,0 +1,16 @@
from macropy.core import parse_stmt
from macropy.core.macros import Macros
from ast import ClassDef
macros = Macros()
@macros.decorator
def test_macro(tree, **kw):
print(tree)
if isinstance(tree, ClassDef) and len(tree.bases) == 1:
pre = parse_stmt("from base_class import BaseClass")
tree.bases[0].id = 'BaseClass'
post = parse_stmt("print('Hola')")
return [pre, tree, post]
return tree

View File

@ -0,0 +1,3 @@
#!/usr/bin/python3
import macropy.activate # noqa: F401
import application # noqa: F401

View File

@ -17,6 +17,7 @@ from .error import (PlotError, KiPlotConfigurationError, config_error)
from .pre_base import BasePreFlight
from . import log
logger = log.get_logger(__name__)
@ -32,7 +33,11 @@ except ImportError: # pragma: no cover
def _import(name, path):
# Python 3.4+ import mechanism
spec = spec_from_file_location("kiplot."+name, path)
# Macropy hook:
from macropy.core import import_hooks
spec = import_hooks.MacroFinder.find_spec("kiplot."+name, [os.path.dirname(path)])
if spec is None:
spec = spec_from_file_location("kiplot."+name, path)
mod = module_from_spec(spec)
spec.loader.exec_module(mod)
@ -48,17 +53,12 @@ def _load_actions(path):
def load_actions():
""" Load all the available ouputs and preflights """
from mcpy import activate
# activate.activate()
_load_actions(os.path.abspath(os.path.dirname(__file__)))
home = os.environ.get('HOME')
if home:
dir = os.path.join(home, '.config', 'kiplot', 'plugins')
if os.path.isdir(dir):
_load_actions(dir)
if 'de_activate' in activate.__dict__:
logger.debug('Deactivating macros')
activate.de_activate()
def check_version(command, version):

View File

@ -1,16 +1,20 @@
from ast import (Assign, Name, Attribute, Expr, Num, Str, NameConstant, Load, Store, UnaryOp, USub,
ClassDef, Call, ImportFrom, alias)
from macropy.core import (parse_stmt)
from macropy.core.macros import Macros
from ast import (Assign, Name, Attribute, Expr, Num, Str, NameConstant, Load, Store, UnaryOp, USub, ClassDef)
macros = Macros()
def document(sentences, to_source, **kw):
@macros.block
def document(tree, **kw):
""" This macro takes literal strings and converts them into:
_help_ID = type_hint+STRING
where:
ID is the first target of the last assignment.
type_hint is the assigned type and default value (only works for a few types)
STRING is the literal string """
for n in range(len(sentences)):
s = sentences[n]
for n in range(len(tree)):
s = tree[n]
if not n:
prev = s
continue
@ -54,10 +58,10 @@ def document(sentences, to_source, **kw):
target = Attribute(value=Name(id='self', ctx=Load()), attr=doc_id, ctx=Store())
else: # pragma: no cover
target = Name(id=doc_id, ctx=Store())
sentences[n] = Assign(targets=[target], value=Str(s=type_hint+s.value.s))
tree[n] = Assign(targets=[target], value=Str(s=type_hint+s.value.s))
prev = s
# Return the modified AST
return sentences
return tree
def _do_wrap_class_register(tree, mod, base_class):
@ -65,23 +69,17 @@ def _do_wrap_class_register(tree, mod, base_class):
# Create the register call
name = tree.name
reg_name = name.lower()
# BaseOutput.register member:
attr = Attribute(value=Name(id=base_class, ctx=Load()), attr='register', ctx=Load())
# Function call to it passing reg_name and name
do_register = Expr(value=Call(func=attr, args=[Str(s=reg_name), Name(id=name, ctx=Load())], keywords=[]))
# Create the import
do_import = ImportFrom(module=mod, names=[alias(name=base_class, asname=None)], level=1)
return [do_import, tree, do_register]
do_import = parse_stmt("from kiplot.{} import {}".format(mod, base_class))
do_register = parse_stmt("{}.register('{}', {})".format(base_class, reg_name, name))
return [tree, do_import, do_register]
# Just in case somebody applies it to anything other than a class
return tree # pragma: no cover
@macros.decorator
def output_class(tree, **kw):
"""A decorator to wrap a class with:
from .out_base import BaseOutput
... Class definition
BaseOutput.register(CLASS_NAME_LOWER_STRING, CLASS_NAME)
@ -89,10 +87,10 @@ def output_class(tree, **kw):
return _do_wrap_class_register(tree, 'out_base', 'BaseOutput')
@macros.decorator
def pre_class(tree, **kw):
"""A decorator to wrap a class with:
from .pre_base import BasePreFlight
... Class definition
BasePreFlight.register(CLASS_NAME_LOWER_STRING, CLASS_NAME)

View File

@ -1,5 +1,6 @@
from pcbnew import EXCELLON_WRITER
from .out_any_drill import AnyDrill
from kiplot.out_any_drill import AnyDrill
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
@ -24,7 +25,7 @@ class ExcellonOptions(AnyDrill):
@output_class
class Excellon(BaseOutput): # noqa: F821
class Excellon(BaseOutput):
""" Excellon drill format
This is the main format for the drilling machine.
You can create a map file for documentation purposes.

View File

@ -1,5 +1,6 @@
from pcbnew import GERBER_WRITER
from .out_any_drill import AnyDrill
from kiplot.out_any_drill import AnyDrill
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
@ -16,7 +17,7 @@ class Gerb_DrillOptions(AnyDrill):
@output_class
class Gerb_Drill(BaseOutput): # noqa: F821
class Gerb_Drill(BaseOutput):
""" Gerber drill format
This is the information for the drilling machine in gerber format.
You can create a map file for documentation purposes.

View File

@ -1,6 +1,6 @@
from pcbnew import (PLOT_FORMAT_GERBER, FromMM, ToMM)
from .out_any_layer import (AnyLayer, AnyLayerOptions)
from .error import KiPlotConfigurationError
from kiplot.out_any_layer import (AnyLayer, AnyLayerOptions)
from kiplot.error import KiPlotConfigurationError
from kiplot.macros import macros, document, output_class # noqa: F401

View File

@ -1,13 +1,14 @@
import os
from subprocess import (check_output, STDOUT, CalledProcessError)
from .misc import (CMD_IBOM, URL_IBOM, BOM_ERROR)
from .gs import (GS)
from .kiplot import (check_script)
from .optionable import BaseOptions
from kiplot.misc import (CMD_IBOM, URL_IBOM, BOM_ERROR)
from kiplot.gs import (GS)
from kiplot.kiplot import (check_script)
from kiplot.optionable import BaseOptions
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log
from kiplot.log import get_logger
logger = log.get_logger(__name__)
logger = get_logger(__name__)
class IBoMOptions(BaseOptions):
@ -98,7 +99,7 @@ class IBoMOptions(BaseOptions):
@output_class
class IBoM(BaseOutput): # noqa: F821
class IBoM(BaseOutput):
""" IBoM (Interactive HTML BoM)
Generates an interactive web page useful to identify the position of the components in the PCB.
For more information: https://github.com/INTI-CMNB/InteractiveHtmlBom

View File

@ -1,10 +1,11 @@
import os
from glob import (glob)
from glob import glob
from subprocess import (check_output, STDOUT, CalledProcessError)
from .misc import (CMD_KIBOM, URL_KIBOM, BOM_ERROR)
from .kiplot import (check_script)
from .gs import (GS)
from .optionable import BaseOptions
from kiplot.misc import (CMD_KIBOM, URL_KIBOM, BOM_ERROR)
from kiplot.kiplot import check_script
from kiplot.gs import GS
from kiplot.optionable import BaseOptions
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log
@ -60,7 +61,7 @@ class KiBoMOptions(BaseOptions):
@output_class
class KiBoM(BaseOutput): # noqa: F821
class KiBoM(BaseOutput):
""" KiBoM (KiCad Bill of Materials)
Used to generate the BoM in HTML or CSV format using the KiBoM plug-in.
For more information: https://github.com/INTI-CMNB/KiBoM

View File

@ -1,11 +1,12 @@
import os
import re
from tempfile import (NamedTemporaryFile)
from subprocess import (check_output, STDOUT, CalledProcessError)
from .misc import (PCBDRAW, PCBDRAW_ERR)
from .error import KiPlotConfigurationError
from .gs import (GS)
from .optionable import (BaseOptions, Optionable)
from tempfile import NamedTemporaryFile
from subprocess import check_output, STDOUT, CalledProcessError
from kiplot.misc import PCBDRAW, PCBDRAW_ERR
from kiplot.error import KiPlotConfigurationError
from kiplot.gs import GS
from kiplot.optionable import BaseOptions, Optionable
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log
@ -233,7 +234,7 @@ class PcbDrawOptions(BaseOptions):
@output_class
class PcbDraw(BaseOutput): # noqa: F821
class PcbDraw(BaseOutput):
""" PcbDraw - Beautiful 2D PCB render
Exports the PCB as a 2D model (SVG, PNG or JPG).
Uses configurable colors.

View File

@ -2,9 +2,9 @@ from pcbnew import (PLOT_FORMAT_PDF, FromMM, ToMM)
from kiplot.out_any_layer import AnyLayer
from kiplot.drill_marks import DrillMarks
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log
from kiplot.log import get_logger
logger = log.get_logger(__name__)
logger = get_logger(__name__)
class PDFOptions(DrillMarks):

View File

@ -1,16 +1,17 @@
import os
from subprocess import (call)
from .pre_base import BasePreFlight
from .error import (KiPlotConfigurationError)
from .gs import (GS)
from .kiplot import (check_script)
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT)
from .optionable import BaseOptions
from kiplot.pre_base import BasePreFlight
from kiplot.error import KiPlotConfigurationError
from kiplot.gs import GS
from kiplot.kiplot import check_script
from kiplot.misc import CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT
from kiplot.optionable import BaseOptions
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
from .layer import Layer
from . import log
from kiplot.layer import Layer
from kiplot.log import get_logger
logger = log.get_logger(__name__)
logger = get_logger(__name__)
class PDF_Pcb_PrintOptions(BaseOptions):
@ -48,7 +49,7 @@ class PDF_Pcb_PrintOptions(BaseOptions):
@output_class
class PDF_Pcb_Print(BaseOutput): # noqa: F821
class PDF_Pcb_Print(BaseOutput):
""" PDF PCB Print (Portable Document Format)
Exports the PCB to the most common exhange format. Suitable for printing.
This is the main format to document your PCB.

View File

@ -1,13 +1,14 @@
import os
from subprocess import (call)
from .gs import (GS)
from .kiplot import (check_eeschema_do)
from .misc import (CMD_EESCHEMA_DO, PDF_SCH_PRINT)
from .optionable import BaseOptions
from subprocess import call
from kiplot.gs import GS
from kiplot.kiplot import check_eeschema_do
from kiplot.misc import CMD_EESCHEMA_DO, PDF_SCH_PRINT
from kiplot.optionable import BaseOptions
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log
from kiplot.log import get_logger
logger = log.get_logger(__name__)
logger = get_logger(__name__)
class PDF_Sch_PrintOptions(BaseOptions):
@ -36,7 +37,7 @@ class PDF_Sch_PrintOptions(BaseOptions):
@output_class
class PDF_Sch_Print(BaseOutput): # noqa: F821
class PDF_Sch_Print(BaseOutput):
""" PDF Schematic Print (Portable Document Format)
Exports the PCB to the most common exhange format. Suitable for printing.
This is the main format to document your schematic.

View File

@ -2,8 +2,9 @@ import os
import operator
from datetime import datetime
from pcbnew import (IU_PER_MM, IU_PER_MILS)
from .optionable import BaseOptions
from kiplot.optionable import BaseOptions
from kiplot.macros import macros, document, output_class # noqa: F401
from kiplot.out_base import BaseOutput
class PositionOptions(BaseOptions):
@ -152,7 +153,7 @@ class PositionOptions(BaseOptions):
@output_class
class Position(BaseOutput): # noqa: F821
class Position(BaseOutput):
""" Pick & place
Generates the file with position information for the PCB components, used by the pick and place machine.
This output is what you get from the 'File/Fabrication output/Footprint poistion (.pos) file' menu in pcbnew. """

View File

@ -1,14 +1,15 @@
import os
import re
from subprocess import (check_output, STDOUT, CalledProcessError)
from .error import KiPlotConfigurationError
from .misc import (KICAD2STEP, KICAD2STEP_ERR)
from .gs import (GS)
from .optionable import BaseOptions
from subprocess import check_output, STDOUT, CalledProcessError
from kiplot.error import KiPlotConfigurationError
from kiplot.misc import KICAD2STEP, KICAD2STEP_ERR
from kiplot.gs import GS
from kiplot.optionable import BaseOptions
from kiplot.out_base import BaseOutput
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log
from kiplot.log import get_logger
logger = log.get_logger(__name__)
logger = get_logger(__name__)
class STEPOptions(BaseOptions):
@ -79,7 +80,7 @@ class STEPOptions(BaseOptions):
@output_class
class STEP(BaseOutput): # noqa: F821
class STEP(BaseOutput):
""" STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
Exports the PCB as a 3D model.
This is the most common 3D format for exchange purposes.

View File

@ -1,9 +1,10 @@
from .error import (KiPlotConfigurationError)
from kiplot.error import KiPlotConfigurationError
from kiplot.pre_base import BasePreFlight
from kiplot.macros import macros, pre_class # noqa: F401
@pre_class
class Check_Zone_Fills(BasePreFlight): # noqa: F821
class Check_Zone_Fills(BasePreFlight):
""" [boolean=false] Zones are filled before doing any operation involving PCB layers """
def __init__(self, name, value):
super().__init__(name, value)
@ -12,4 +13,4 @@ class Check_Zone_Fills(BasePreFlight): # noqa: F821
self._enabled = value
def apply(self):
BasePreFlight._set_option('check_zone_fills', self._enabled) # noqa: F821
BasePreFlight._set_option('check_zone_fills', self._enabled)

View File

@ -1,17 +1,18 @@
from sys import (exit)
from subprocess import (call)
from sys import exit
from subprocess import call
from kiplot.macros import macros, pre_class # noqa: F401
from .error import (KiPlotConfigurationError)
from .gs import (GS)
from .kiplot import (check_script)
from .misc import (CMD_PCBNEW_RUN_DRC, URL_PCBNEW_RUN_DRC, DRC_ERROR)
from .log import (get_logger)
from kiplot.error import KiPlotConfigurationError
from kiplot.gs import GS
from kiplot.kiplot import check_script
from kiplot.misc import CMD_PCBNEW_RUN_DRC, URL_PCBNEW_RUN_DRC, DRC_ERROR
from kiplot.pre_base import BasePreFlight
from kiplot.log import get_logger
logger = get_logger(__name__)
@pre_class
class Run_DRC(BasePreFlight): # noqa: F821
class Run_DRC(BasePreFlight):
""" [boolean=false] Runs the DRC (Distance Rules Check). To ensure we have a valid PCB """
def __init__(self, name, value):
super().__init__(name, value)
@ -25,7 +26,7 @@ class Run_DRC(BasePreFlight): # noqa: F821
cmd = [CMD_PCBNEW_RUN_DRC, 'run_drc']
if GS.filter_file:
cmd.extend(['-f', GS.filter_file])
if BasePreFlight.get_option('ignore_unconnected'): # noqa: F821
if BasePreFlight.get_option('ignore_unconnected'):
cmd.append('-i')
cmd.extend([GS.pcb_file, GS.out_dir])
# If we are in verbose mode enable debug in the child

View File

@ -1,17 +1,18 @@
from sys import (exit)
from subprocess import (call)
from sys import exit
from subprocess import call
from kiplot.macros import macros, pre_class # noqa: F401
from .gs import (GS)
from .kiplot import (check_eeschema_do)
from .error import (KiPlotConfigurationError)
from .misc import (CMD_EESCHEMA_DO, ERC_ERROR)
from .log import (get_logger)
from kiplot.pre_base import BasePreFlight
from kiplot.gs import GS
from kiplot.kiplot import check_eeschema_do
from kiplot.error import KiPlotConfigurationError
from kiplot.misc import CMD_EESCHEMA_DO, ERC_ERROR
from kiplot.log import get_logger
logger = get_logger(__name__)
@pre_class
class Run_ERC(BasePreFlight): # noqa: F821
class Run_ERC(BasePreFlight):
""" [boolean=false] Runs the ERC (Electrical Rules Check). To ensure the schematic is electrically correct """
def __init__(self, name, value):
super().__init__(name, value)

View File

@ -1,10 +1,11 @@
import os
from .gs import (GS)
from kiplot.gs import GS
from kiplot.pre_base import BasePreFlight
from kiplot.macros import macros, pre_class # noqa: F401
@pre_class
class Filters(BasePreFlight): # noqa: F821
class Filters(BasePreFlight):
""" A list of entries to filter out ERC/DRC messages. Keys: `filter`, `number` and `regex` """
def __init__(self, name, value):
super().__init__(name, value)

View File

@ -1,9 +1,10 @@
from kiplot.macros import macros, pre_class # noqa: F401
from .error import (KiPlotConfigurationError)
from kiplot.pre_base import BasePreFlight
from kiplot.error import KiPlotConfigurationError
@pre_class
class Ignore_Unconnected(BasePreFlight): # noqa: F821
class Ignore_Unconnected(BasePreFlight):
""" [boolean=false] Option for `run_drc`. Ignores the unconnected nets. Useful if you didn't finish the routing """
def __init__(self, name, value):
super().__init__(name, value)
@ -16,4 +17,4 @@ class Ignore_Unconnected(BasePreFlight): # noqa: F821
return 'false'
def apply(self):
BasePreFlight._set_option('ignore_unconnected', self._enabled) # noqa: F821
BasePreFlight._set_option('ignore_unconnected', self._enabled)

View File

@ -1,17 +1,18 @@
from sys import (exit)
from subprocess import (call)
from sys import exit
from subprocess import call
from kiplot.macros import macros, pre_class # noqa: F401
from .error import (KiPlotConfigurationError)
from .gs import (GS)
from .kiplot import (check_eeschema_do)
from .misc import (CMD_EESCHEMA_DO, BOM_ERROR)
from .log import (get_logger)
from kiplot.pre_base import BasePreFlight
from kiplot.error import KiPlotConfigurationError
from kiplot.gs import GS
from kiplot.kiplot import check_eeschema_do
from kiplot.misc import CMD_EESCHEMA_DO, BOM_ERROR
from kiplot.log import get_logger
logger = get_logger(__name__)
@pre_class
class Update_XML(BasePreFlight): # noqa: F821
class Update_XML(BasePreFlight):
""" [boolean=false] Update the XML version of the BoM (Bill of Materials). To ensure our generated BoM is up to date """
def __init__(self, name, value):
super().__init__(name, value)

View File

@ -1,20 +1,3 @@
#!/usr/bin/env python3
"""
@package
KiPlot - KiCad Python API plotter
KiPlot is a program which helps you to plot your KiCad PCBs to output
formats easily, repeatable, and most of all, scriptably. This means you
can use a Makefile to export your KiCad PCBs just as needed.
"""
import sys
import os
here = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.dirname(here))
from kiplot.__main__ import main
main()
#!/usr/bin/python3
import macropy.activate # noqa: F401
import run_kiplot # noqa: F401

17
src/run_kiplot.py Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""
@package
KiPlot - KiCad Python API plotter
KiPlot is a program which helps you to plot your KiCad PCBs to output
formats easily, repeatable, and most of all, scriptably. This means you
can use a Makefile to export your KiCad PCBs just as needed.
"""
import sys
import os
here = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.dirname(here))
from kiplot.__main__ import main
main()