From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id BC3CD431FC; Wed, 25 Oct 2023 15:33:27 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 882AA402D0; Wed, 25 Oct 2023 15:33:27 +0200 (CEST) Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.8]) by mails.dpdk.org (Postfix) with ESMTP id C576C40273 for ; Wed, 25 Oct 2023 15:33:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698240806; x=1729776806; h=date:from:to:cc:subject:message-id:references: in-reply-to:mime-version; bh=chIpj5edA2lQZW38LE5+jhHc6HIk30gnLkEUPMtgzVM=; b=CSprJu688I35oWjXXKCJ5XyPpSrSvn1s6dqRx6BZd2xeEmiccp0Hg7zU LbLvDNyHszcLVx2TeZuTIMdRHKbl6RX2fy9GOGkl9Uvk4Y562dqQK+qXZ fhjgv2b80hqdSmuw4BnkPeSJGbDqum70YY8BpYok/ocJ8WMiY3I4PNTYy Z8D9oJ+qk2CbHkRxJOj0yrODCCYyFCR3waxjELqVhN4k7uH/R/ykq36PY ETdqwExbXgLdkFZe5/+4bZUEFZWefX91WDvA08+nA1gSwcE3T8ffsaK4/ 14PsFeUTJAggDNsXTvfkO9BVUYeDJqwyxxdZ0lOZm2+HOzyiH6ZVKqPdO g==; X-IronPort-AV: E=McAfee;i="6600,9927,10874"; a="61102" X-IronPort-AV: E=Sophos;i="6.03,250,1694761200"; d="scan'208";a="61102" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by fmvoesa102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 25 Oct 2023 06:33:25 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10874"; a="1090221811" X-IronPort-AV: E=Sophos;i="6.03,250,1694761200"; d="scan'208";a="1090221811" Received: from fmsmsx602.amr.corp.intel.com ([10.18.126.82]) by fmsmga005.fm.intel.com with ESMTP/TLS/AES256-GCM-SHA384; 25 Oct 2023 06:33:24 -0700 Received: from fmsmsx610.amr.corp.intel.com (10.18.126.90) by fmsmsx602.amr.corp.intel.com (10.18.126.82) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.32; Wed, 25 Oct 2023 06:33:24 -0700 Received: from fmsedg601.ED.cps.intel.com (10.1.192.135) by fmsmsx610.amr.corp.intel.com (10.18.126.90) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.34 via Frontend Transport; Wed, 25 Oct 2023 06:33:24 -0700 Received: from NAM10-MW2-obe.outbound.protection.outlook.com (104.47.55.101) by edgegateway.intel.com (192.55.55.70) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.32; Wed, 25 Oct 2023 06:33:24 -0700 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=ljDZtw4VcAWzbOinOY+iGg1zIp8l5H1H6npLqJhqwwnE+jy9D4tFYvLALA/s0zMsy2mp1HFctl+oDHyAprB07AEMr1vPt3rvcX/DcAYp4Mc7Z+ahPTVdK+lHdMPl302w2wsTwJSlqCewpvp35Z2aY/v2XpgfQDLDn+OmaRT3drIaSqOL+9/q6XC7Eejs2FnWBDYGFNQrlVnJ5fpbtFbSZQ/NGf/qeVZ1I2TwtSuGJDqcOM7gs9pWheV3dY5FnTQUcpz+lMP3DRjjCr2EkSBLovQdsuPI5UYJwfuTY3BQr4IMBBAUVFqOrhvGnP5vhXOEeW4rnrz4vfoJGZ1tHlVMcQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=Y0AzoTMlcve0lunJVAE8JAHDjxEeQIXcg8bPbdbmjpc=; b=EeIDkOTbSKEQYQL0JVRwFVvA9emzoLzKVWl2MEvWS+1lUmvrq/dhOhUSlgw7Ak3nwYDsEAUyGxntmY/iv4ZgYbaPZFgfZG89PRI58jCw56xAWwO1vZynu65ElK3uZbLMkGKUIe/M+ygLq/bwrjOw9YYc+3BtQTzCCvhO0Z3lLYO3S2AMhFrQSAzWIpp4IRXyrk5MX9WayMY9f1ftmFYMrfk/gtS7+ceV5jpB98esfVJyPN6UBJjki3XfSgqS2cWDsRpK1cNHwjOVqNpKtS9sQ0kyB36T3EktiP9WFFvPif81raf0hykDw0ZeJacKmsU1K5/U5NuxHiK0URfERn7Xbg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=intel.com; dmarc=pass action=none header.from=intel.com; dkim=pass header.d=intel.com; arc=none Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=intel.com; Received: from DS0PR11MB7309.namprd11.prod.outlook.com (2603:10b6:8:13e::17) by SA1PR11MB5924.namprd11.prod.outlook.com (2603:10b6:806:23b::14) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6907.33; Wed, 25 Oct 2023 13:33:16 +0000 Received: from DS0PR11MB7309.namprd11.prod.outlook.com ([fe80::d70b:11a0:d28f:ec44]) by DS0PR11MB7309.namprd11.prod.outlook.com ([fe80::d70b:11a0:d28f:ec44%6]) with mapi id 15.20.6907.032; Wed, 25 Oct 2023 13:33:16 +0000 Date: Wed, 25 Oct 2023 14:33:10 +0100 From: Bruce Richardson To: Robin Jarry CC: , Subject: Re: [PATCH v5 2/9] buildtools: script to generate cmdline boilerplate Message-ID: References: <20230802170052.955323-1-bruce.richardson@intel.com> <20231017121318.146007-1-bruce.richardson@intel.com> <20231017121318.146007-3-bruce.richardson@intel.com> Content-Type: text/plain; charset="us-ascii" Content-Disposition: inline In-Reply-To: X-ClientProxiedBy: DU2PR04CA0265.eurprd04.prod.outlook.com (2603:10a6:10:28e::30) To DS0PR11MB7309.namprd11.prod.outlook.com (2603:10b6:8:13e::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS0PR11MB7309:EE_|SA1PR11MB5924:EE_ X-MS-Office365-Filtering-Correlation-Id: 7324e8fa-bf1c-409e-1488-08dbd55ef194 X-LD-Processed: 46c98d88-e344-4ed4-8496-4ed7712e255d,ExtAddr X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: ZyGsh5Fe1KHoB9NeCrBHSeKBQVrVSAosccpkGJcVjg52VafdU2yPoHPS/kKH7Z/ZBOeKmPd8lmVlbtk6Du4YnSsnm8BUqZt/GCvMo+WD5SaIlBDfNdwTNci2Kb6+90YrYVKN6jPcibwMb9GGPMeaNnL8dO1CvtAzzLXoIZxG/gRaP9eirAdrx/gYERMz6ZvnuBK05/yBBFQEEypWyTjGUUZFhZLrmeXvb43i8dAFGxMkD4QP8Lq/QiiI+pr+ezlcjd5QSmOm89P/XlMxOn0rgd+O+zRrYVEADzicOcGxMSYvaIDehuPM6KfIRTabReBrcbg5Bdo6Ju9CaE+pS7GN49M8yAy5irGWyBfvipL8TYT0a/Wzjg5V7L2zEhIZIE9Zyn1rp91f13PQ/mFnBffmwK/McgrQGL4DY5bhisVNaTx2zU+7xi3EC6gsG+goqXLRgq1nearw3oQ/ltrVrTcfSauMxRjC+DSmz/CpRMOgeRhelMO4msqSAfNNjeZ0CBp/XdL8J+yxTeYN/cNn3hginXqGV/OdFKipl7ocEXDy5d5KPfolUTPU8cosInHTxrfy X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:DS0PR11MB7309.namprd11.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230031)(136003)(39860400002)(366004)(396003)(346002)(376002)(230922051799003)(186009)(64100799003)(451199024)(1800799009)(83380400001)(4326008)(8936002)(8676002)(26005)(66899024)(82960400001)(38100700002)(86362001)(2906002)(5660300002)(44832011)(41300700001)(6486002)(6506007)(478600001)(66946007)(6666004)(6512007)(6916009)(66556008)(66476007)(316002); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?cDPn1FRg6yo/tAp1bAbE0J6YwwoLUNYGYkRuDd9w8QS2+lyYwnRkoiO2zfGw?= =?us-ascii?Q?6Ls2+o/his2T3au1jYV0KNyaQHYUMQXw/IuB85AD1tOO+DaGaSEeHUndtyWg?= =?us-ascii?Q?P0vUuXGsIUXQ4J2tpzCCpQPmknY8LQKe9ObVGOOoUueGVUA/lwE6zAUlBO8X?= =?us-ascii?Q?XI5iCXtiiwLPUgwRCxx4MbCMZA92YyYQKdTHeIXu/1cUsOBD27t19acEqIxK?= =?us-ascii?Q?A3kM9nAZ1y9mDnvWOQ6GwEReWV5Cia0kzCdZFenh9ZyaQT6j2/K6A4ZHvhXr?= =?us-ascii?Q?BNVasdlyA+XobfZc0PYFb9XH4TM4Fqxm7OMLDd31emPq6TzNlzTYZ9LWwY3Z?= =?us-ascii?Q?6l7ZOjp1sRKiyVOHX18i+ty5gSIFmxmpf0SMeNyvHH/L6nL4zU0xYXFNC/Lj?= =?us-ascii?Q?Je/rNZepcM5ghcPEFSkrcmU5HAaNlfsszqaIWTWtIxuKGoY8Waw4jsssrc1X?= =?us-ascii?Q?h8pH/FA11ntrzPo/FrM+yGcZdgOXyxbGLJnbbVu2Z2YJYBMl61J/iE3SZO5T?= =?us-ascii?Q?lOEPz6BpaGpGp59hbtdfDOB72XhXzHxXt71flhS6cV199VzauHsMrP+0xiCl?= =?us-ascii?Q?YP9F4GKySU5XyMHatQUfhL5GUVfty21VNMNmmp0uj589MsLpqW3WBQsbEI90?= =?us-ascii?Q?VYLZxo3WAA1fpdL/ZfEvgAb++wqoulClTNRA4J1vdDJBIJnZhsa1zOhyeGDu?= =?us-ascii?Q?7oaBrl2qvckxE3KbrPhQnL1I9nWqQExrxptAoeeqSyHu45fEHsEkF0ghm4iK?= =?us-ascii?Q?RuRn1nmUWIxrOEFybIMA9wFu5WGMmoLtfwHy5uMUIIhuWqJQ0iMdCgO0/dRy?= =?us-ascii?Q?CiUyupfNsd0yj2r8z1Tu1GyMQSIMJ3QLpTSA22+6QRSVnGJ0KLoSGWRsa4pg?= =?us-ascii?Q?HH6yQtsgyTXOFdiLerVVH7YdW4I6RH+ujDI5K8z/JI7PfpgYJHSMvTV5i9VY?= =?us-ascii?Q?3nQfxqkOHxqsnLu7IIDyFcn1hNTZylIg19MyfCEvq59Qnqbh0dcLDnCxZGYe?= =?us-ascii?Q?eJzTudyppfkT9zmud0Hj3CucDSWRJ8enQzW/q1SCLBD96winDQxaB8f4mZHb?= =?us-ascii?Q?hEy8pABvxIjQPomBNHuKJRPpnDA7fik+wHvcMdE9byZGSQF5IWEl6OYRlDmF?= =?us-ascii?Q?WjmJdUvhXU66RcoZ6GuwgXV8DRJDYcPjFMzik+kEB1A/v8e0p4dGrDV/H3xj?= =?us-ascii?Q?5Zf/uQPKJ39AZYWN5yEbIw+6qdCrbhuqWFo0zZmnQg1Me7U3p2cDdxT9BvGE?= =?us-ascii?Q?tz7iAYnV+I4SE0J2fgvemPKCd7De4L9MluNTHa9Aj22XCADDLav0ReMsIMvD?= =?us-ascii?Q?TkYK8djFUysdzbk86Xsr2qh5gjf+Remyk1O67FVp7S4JHgiqi9yk0/o/Qr9K?= =?us-ascii?Q?4B41sS7aXjtkNIUNU5XVrYAbegP8fdbfqpNUbD2y5yw05NF4uZs0x3JozmbL?= =?us-ascii?Q?55qfmfYrX0s2X+0qJ6fsDXmlAzF413jkd5ADsqwRXjYisPV3KEqaIDpY5f4s?= =?us-ascii?Q?kfB4Y1du5+j6XSsV2pMuiHLH1UCdB1JeCFHs1K7gJ6Fwt/dFIJw0VCWS6EBT?= =?us-ascii?Q?Bwm9/kmCdCeArxN3EZXMq5xTIeKLFincwZqUc2UEhNE8Y4WofQrOeJ/05ZGq?= =?us-ascii?Q?XQ=3D=3D?= X-MS-Exchange-CrossTenant-Network-Message-Id: 7324e8fa-bf1c-409e-1488-08dbd55ef194 X-MS-Exchange-CrossTenant-AuthSource: DS0PR11MB7309.namprd11.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Oct 2023 13:33:16.2173 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 46c98d88-e344-4ed4-8496-4ed7712e255d X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: yHYAw8vjdxJSZksEvCfYiTYFuYKBA3b2r1G35MZQi80f3zci/TrYBtgPaFF5v/DyY/ifqNcoYJAPeHP8QTxJCVVxiZuiEid9zm+80hr5bNg= X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR11MB5924 X-OriginatorOrg: intel.com X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org On Wed, Oct 25, 2023 at 03:04:05PM +0200, Robin Jarry wrote: > Bruce Richardson, Oct 17, 2023 at 14:13: > > Provide a "dpdk-cmdline-gen.py" script for application developers to > > quickly generate the boilerplate code necessary for using the cmdline > > library. > > > > Example of use: > > The script takes an input file with a list of commands the user wants in > > the app, where the parameter variables are tagged with the type. > > For example: > > > > $ cat commands.list > > list > > add x y > > echo message > > add socket path > > quit > > > > When run through the script as "./dpdk-cmdline-gen.py commands.list", > > the output will be the contents of a header file with all the > > boilerplate necessary for a commandline instance with those commands. > > > > If the flag --stubs is passed, an output header filename must also be > > passed, in which case both a header file with the definitions and a C > > file with function stubs in it is written to disk. The separation is so > > that the header file can be rewritten at any future point to add more > > commands, while the C file can be kept as-is and extended by the user > > with any additional functions needed. > > > > Signed-off-by: Bruce Richardson > > --- > > buildtools/dpdk-cmdline-gen.py | 190 ++++++++++++++++++++++++++++++ > > buildtools/meson.build | 7 ++ > > doc/guides/prog_guide/cmdline.rst | 131 +++++++++++++++++++- > > 3 files changed, 327 insertions(+), 1 deletion(-) > > create mode 100755 buildtools/dpdk-cmdline-gen.py > > Hi Bruce, > > thanks for the respin! I have some small remarks inline. > > > diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py > > new file mode 100755 > > index 0000000000..6cb7610de4 > > --- /dev/null > > +++ b/buildtools/dpdk-cmdline-gen.py > > @@ -0,0 +1,190 @@ > > +#!/usr/bin/env python3 > > +# SPDX-License-Identifier: BSD-3-Clause > > +# Copyright(c) 2023 Intel Corporation > > +# > > +""" > > +Script to automatically generate boilerplate for using DPDK cmdline library. > > +""" > > + > > +import argparse > > +import sys > > + > > +PARSE_FN_PARAMS = "void *parsed_result, struct cmdline *cl, void *data" > > +PARSE_FN_BODY = """ > > + /* TODO: command action */ > > + RTE_SET_USED(parsed_result); > > + RTE_SET_USED(cl); > > + RTE_SET_USED(data); > > +""" > > +NUMERIC_TYPES = [ > > + "UINT8", > > + "UINT16", > > + "UINT32", > > + "UINT64", > > + "INT8", > > + "INT16", > > + "INT32", > > + "INT64", > > +] > > + > > + > > +def process_command(lineno, tokens, comment): > > + """Generate the structures and definitions for a single command.""" > > + out = [] > > + cfile_out = [] > > + > > + if tokens[0].startswith("<"): > > + raise ValueError(f"Error line {lineno + 1}: command must start with a literal string") > > + > > + name_tokens = [] > > + for t in tokens: > > + if t.startswith("<"): > > + break > > + name_tokens.append(t) > > + name = "_".join(name_tokens) > > + > > + result_struct = [] > > + initializers = [] > > + token_list = [] > > + for t in tokens: > > + if t.startswith("<"): > > + t_type, t_name = t[1:].split(">") > > + t_val = "NULL" > > + else: > > + t_type = "STRING" > > + t_name = t > > + t_val = f'"{t}"' > > + > > + if t_type == "STRING": > > + result_struct.append(f"\tcmdline_fixed_string_t {t_name};") > > + initializers.append( > > + f"static cmdline_parse_token_string_t cmd_{name}_{t_name}_tok =\n" > > + + f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});" > > Since you are now using multiline strings in process_commands(), why not use > them everywhere? > > It would make the code more readable in my opinion and would avoid inline > f-string concatenation. > I'm a bit unsure about this case. I notice I can at least remove the "+" symbol and have implicit string concat, but I really don't like the way the indentation gets adjusted when we use multi-line strings, since the indent has to match the C-code indent rather than the python indentation levels. Therefore, I'm going to leave these pairs of lines as they are. > > + ) > > + elif t_type in NUMERIC_TYPES: > > + result_struct.append(f"\t{t_type.lower()}_t {t_name};") > > + initializers.append( > > + f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n" > > + + f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});" > > + ) > > + elif t_type in ["IP", "IP_ADDR", "IPADDR"]: > > + result_struct.append(f"\tcmdline_ipaddr_t {t_name};") > > + initializers.append( > > + f"cmdline_parse_token_ipaddr_t cmd_{name}_{t_name}_tok =\n" > > + + f"\tTOKEN_IPV4_INITIALIZER(struct cmd_{name}_result, {t_name});" > > + ) > > + else: > > + raise TypeError(f"Error line {lineno + 1}: unknown token type '{t_type}'") > > + token_list.append(f"cmd_{name}_{t_name}_tok") > > + > > + out.append(f'/* Auto-generated handling for command "{" ".join(tokens)}" */') > > + # output function prototype > > + func_sig = f"void\ncmd_{name}_parsed({PARSE_FN_PARAMS})" > > + out.append(f"extern {func_sig};\n") > > + # output result data structure > > + out.append(f"struct cmd_{name}_result {{\n" + "\n".join(result_struct) + "\n};\n") > > + # output the initializer tokens > > + out.append("\n".join(initializers) + "\n") > > + # output the instance structure > > + out.append( > > + f"static cmdline_parse_inst_t cmd_{name} = {{\n" > > + + f"\t.f = cmd_{name}_parsed,\n" > > + + "\t.data = NULL,\n" > > + + f'\t.help_str = "{comment}",\n' > > + + "\t.tokens = {" > > Especially here :) > I'll see about converting this and what it looks like. Maybe see if textwrap.dedent will allow sensible indentation with it. > > + ) > > + for t in token_list: > > + out.append(f"\t\t(void *)&{t},") > > + out.append("\t\tNULL\n" + "\t}\n" + "};\n") > > + # output function template if C file being written > > + cfile_out.append(f"{func_sig}\n{{{PARSE_FN_BODY}}}\n") > > + > > + # return the instance structure name > > + return (f"cmd_{name}", out, cfile_out) > > + > > + > > +def process_commands(infile, hfile, cfile, ctxname): > > + """Generate boilerplate output for a list of commands from infile.""" > > + instances = [] > > + > > + hfile.write( > > + f"""/* File autogenerated by {sys.argv[0]} */ > > +#ifndef GENERATED_COMMANDS_H > > +#define GENERATED_COMMANDS_H > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +""" > > + ) > > + > > + for lineno, line in enumerate(infile.readlines()): > > + if line.lstrip().startswith("#"): > > + continue > > + if "#" not in line: > > + line = line + "#" # ensure split always works, even if no help text > > + tokens, comment = line.split("#", 1) > > + cmd_inst, h_out, c_out = process_command(lineno, tokens.strip().split(), comment.strip()) > > + hfile.write("\n".join(h_out)) > > + if cfile: > > + cfile.write("\n".join(c_out)) > > + instances.append(cmd_inst) > > + > > + inst_join_str = ",\n\t&" > > + hfile.write( > > + f""" > > +static __rte_used cmdline_parse_ctx_t {ctxname}[] = {{ > > +\t&{inst_join_str.join(instances)}, > > +\tNULL > > +}}; > > + > > +#endif /* GENERATED_COMMANDS_H */ > > +""" > > By the way, you can put literal tabs in the multiline strings. That way the > indentation will look the same than in the generated C code. > > f""" > static __rte_used cmdline_parse_ctx_t {ctxname}[] = {{ > &{inst_join_str.join(instances)}, > NULL > }}; > > #endif /* GENERATED_COMMANDS_H */ > """ > Trouble with that is that for some editors when using python, the tabs are automatically expanded with spaces. For me using eclipse right now, I physically can't insert leading tabs into the file! I need to open in vim or some other editor to do it (or maybe use copy-paste from a C file), and while that's not a big deal for me, it will be a problem for anyone else editing this in future who has similar settings. /Bruce