Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #51215 > unrolled thread
| Started by | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| First post | 2013-07-25 09:24 -0400 |
| Last post | 2013-07-27 16:15 +0100 |
| Articles | 17 — 8 participants |
Back to article view | Back to comp.lang.python
Critic my module Devyn Collier Johnson <devyncjohnson@gmail.com> - 2013-07-25 09:24 -0400
Re: Critic my module Alain Ketterlin <alain@dpt-info.u-strasbg.fr> - 2013-07-25 16:09 +0200
Re: Critic my module Devyn Collier Johnson <devyncjohnson@gmail.com> - 2013-07-26 06:59 -0400
Re: Critic my module Alister <alister.ware@ntlworld.com> - 2013-07-26 15:08 +0000
Re: Critic my module Joshua Landau <joshua@landau.ws> - 2013-07-26 18:24 +0100
Re: Critic my module Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-07-27 02:48 +0000
Re: Critic my module Devyn Collier Johnson <devyncjohnson@gmail.com> - 2013-07-27 08:56 -0400
Re: Critic my module Alister <alister.ware@ntlworld.com> - 2013-07-27 16:32 +0000
Re: Critic my module Dave Angel <davea@davea.name> - 2013-07-27 12:58 -0400
Re: Critic my module Devyn Collier Johnson <devyncjohnson@gmail.com> - 2013-07-27 09:19 -0400
Re: Critic my module Dave Angel <davea@davea.name> - 2013-07-27 09:35 -0400
Re: Critic my module Chris “Kwpolska” Warrick <kwpolska@gmail.com> - 2013-07-27 15:36 +0200
Re: Critic my module Devyn Collier Johnson <devyncjohnson@gmail.com> - 2013-07-27 09:44 -0400
Re: Critic my module Chris Angelico <rosuav@gmail.com> - 2013-07-27 14:37 +0100
Re: Critic my module Dave Angel <davea@davea.name> - 2013-07-27 10:33 -0400
Re: Critic my module Devyn Collier Johnson <devyncjohnson@gmail.com> - 2013-07-27 10:53 -0400
Re: Critic my module Chris Angelico <rosuav@gmail.com> - 2013-07-27 16:15 +0100
| From | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| Date | 2013-07-25 09:24 -0400 |
| Subject | Critic my module |
| Message-ID | <mailman.5092.1374758683.3114.python-list@python.org> |
[Multipart message — attachments visible in raw view] — view raw
Aloha Python Users!
I made a Python3 module that allows users to use certain Linux shell
commands from Python3 more easily than using os.system(),
subprocess.Popen(), or subprocess.getoutput(). This module (once placed
with the other modules) can be used like this
import boash; boash.ls()
I attached the module. I plan to release it on the Internet soon,
but feel free to use it now. It is licensed under LGPLv3.
The name comes from combining "Boa" with "SHell". Notice that the
module's name almost looks like "BASH", a common Linux shell. The Boa is
a constrictor snake. This module makes Unix shells easier to use via
Python3. This brings the system shell closer to the Python shell.
Mahalo,
Devyn Collier Johnson
DevynCJohnson@Gmail.com
[toc] | [next] | [standalone]
| From | Alain Ketterlin <alain@dpt-info.u-strasbg.fr> |
|---|---|
| Date | 2013-07-25 16:09 +0200 |
| Message-ID | <87y58u20l0.fsf@dpt-info.u-strasbg.fr> |
| In reply to | #51215 |
Devyn Collier Johnson <devyncjohnson@gmail.com> writes:
> I made a Python3 module that allows users to use certain Linux
> shell commands from Python3 more easily than using os.system(),
> subprocess.Popen(), or subprocess.getoutput(). This module (once
> placed with the other modules) can be used like this
Good, but I doubt it's really useful: I think nobody is going to add a
dependency on your module for, basically, one-line wrappers...
Here are a few comments:
> def ls():
> version = '0.3'
> print(subprocess.getoutput('ls'))
version is local here, so basically your first statement is useless
(search for "global" in python's language ref).
> def uname():
> version = '0.3'
> print(platform.uname())
I once learned: "never print anything in a library function". This is a
bad thing to do, for a variety of reasons. For instance, stdout may be
redirected during this call...
> def man(x):
> version = '0.3'
> print(subprocess.getoutput('man' + x))
getoutput is (essentially) Popen(...,shell=True), and the doc says:
"the use of shell=True is strongly discouraged in cases where the
command string is constructed from external input"
(for very good reasons)
> def clear_bash_history():
> version = '0.3'
> print(subprocess.getoutput('history -c'))
Who told you subprocess will use bash? Again, the doc:
"On Unix with shell=True, the shell defaults to /bin/sh."
All your uses of bash-isms may break (esp. "!!")
> def firefox():
> version = '0.3'
> print(subprocess.Popen('(firefox &)'))
See section "Replacing the os.spawn family" in... the doc.
> def go_back():
> version = '0.3'
> print(subprocess.Popen('cd !!:1'))
Hopeless. Have you tried this?
> def reboot():
> version = '0.3'
> print(subprocess.Popen('shutdown -r now'))
What do you expect this to print? I mean, after shutdown/reboot.
> version = '0.6b'
So, what's the version? 0.3 or 0.6b
(btw, are you sure this "version" is the same as the one you use in all
functions?).
-- Alain.
[toc] | [prev] | [next] | [standalone]
| From | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| Date | 2013-07-26 06:59 -0400 |
| Message-ID | <mailman.5144.1374836361.3114.python-list@python.org> |
| In reply to | #51225 |
On 07/25/2013 10:09 AM, Alain Ketterlin wrote:
> Devyn Collier Johnson <devyncjohnson@gmail.com> writes:
>
>> I made a Python3 module that allows users to use certain Linux
>> shell commands from Python3 more easily than using os.system(),
>> subprocess.Popen(), or subprocess.getoutput(). This module (once
>> placed with the other modules) can be used like this
> Good, but I doubt it's really useful: I think nobody is going to add a
> dependency on your module for, basically, one-line wrappers...
>
> Here are a few comments:
>
>> def ls():
>> version = '0.3'
>> print(subprocess.getoutput('ls'))
> version is local here, so basically your first statement is useless
> (search for "global" in python's language ref).
>
>> def uname():
>> version = '0.3'
>> print(platform.uname())
> I once learned: "never print anything in a library function". This is a
> bad thing to do, for a variety of reasons. For instance, stdout may be
> redirected during this call...
>
>> def man(x):
>> version = '0.3'
>> print(subprocess.getoutput('man' + x))
> getoutput is (essentially) Popen(...,shell=True), and the doc says:
>
> "the use of shell=True is strongly discouraged in cases where the
> command string is constructed from external input"
>
> (for very good reasons)
>
>> def clear_bash_history():
>> version = '0.3'
>> print(subprocess.getoutput('history -c'))
> Who told you subprocess will use bash? Again, the doc:
>
> "On Unix with shell=True, the shell defaults to /bin/sh."
>
> All your uses of bash-isms may break (esp. "!!")
>
>> def firefox():
>> version = '0.3'
>> print(subprocess.Popen('(firefox &)'))
> See section "Replacing the os.spawn family" in... the doc.
>
>> def go_back():
>> version = '0.3'
>> print(subprocess.Popen('cd !!:1'))
> Hopeless. Have you tried this?
>
>> def reboot():
>> version = '0.3'
>> print(subprocess.Popen('shutdown -r now'))
> What do you expect this to print? I mean, after shutdown/reboot.
>
>> version = '0.6b'
> So, what's the version? 0.3 or 0.6b
>
> (btw, are you sure this "version" is the same as the one you use in all
> functions?).
>
> -- Alain.
The version in each function is the version of that function if users
want to know what version they are using. The last version is for the
whole module. The module overall is version 0.6b. The module started
with a few functions and as I increased the number of functions, I
increased the module version number. It is a coincidence that all of the
modules happen to have the same version number. I increase the version
number after I work on a function. I cannot remember the command to
print a module's/function's version number, but with that command, you
could see the version of a particular function or module. No, I have not
tried go_back(), thank you for catching that.
The main point of this is for shell users that are using Python and do
not know some of the Python commands. This module would make Python more
like a Linux shell. For instance, a shell user would type boash.uname()
because they may not know they can type "import platform; platform.uname()".
I know that printing is not really the best of ideas, but how else can I
make the output be displayed without quotes or newline marks?
Thank you very much Alain Ketterlin for your feedback!
Mahalo,
DCJ
[toc] | [prev] | [next] | [standalone]
| From | Alister <alister.ware@ntlworld.com> |
|---|---|
| Date | 2013-07-26 15:08 +0000 |
| Message-ID | <ThwIt.13416$Ma6.10382@fx27.am4> |
| In reply to | #51297 |
> > The main point of this is for shell users that are using Python and do > not know some of the Python commands. This module would make Python more > like a Linux shell. For instance, a shell user would type boash.uname() > because they may not know they can type "import platform; > platform.uname()". But they will know how to import your module? to be honest I think this module is a solution looking for a problem & you have re-invented the wheel (Square). don't let that put you off, there are many command line tools that could do with a good wrapper to make them easier to use,perhaps you are tryingto be too general. > > I know that printing is not really the best of ideas, but how else can I > make the output be displayed without quotes or newline marks? as this is a module intended for others to use in their programs (rather than a tool in its own right) the correct approach is to return the result & lave it for the user to print the result, or process it however they require. -- I also never expected Intel to dispose of themselves in such a cute way. - Rik van Riel on linux-kernel
[toc] | [prev] | [next] | [standalone]
| From | Joshua Landau <joshua@landau.ws> |
|---|---|
| Date | 2013-07-26 18:24 +0100 |
| Message-ID | <mailman.5154.1374859517.3114.python-list@python.org> |
| In reply to | #51309 |
[Multipart message — attachments visible in raw view] — view raw
On 26 July 2013 16:08, Alister <alister.ware@ntlworld.com> wrote: > > > > The main point of this is for shell users that are using Python and do > > not know some of the Python commands. This module would make Python more > > like a Linux shell. For instance, a shell user would type boash.uname() > > because they may not know they can type "import platform; > > platform.uname()". > > But they will know how to import your module? > > to be honest I think this module is a solution looking for a problem & > you have re-invented the wheel (Square). > don't let that put you off, there are many command line tools that could > do with a good wrapper to make them easier to use,perhaps you are tryingto > be too general. I actually think http://plumbum.readthedocs.org<http://plumbum.readthedocs.org/en/latest/> , http://sarge.readthedocs.org and http://amoffat.github.io/sh/ do a really good job at this. I might one day even try them ;).
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2013-07-27 02:48 +0000 |
| Message-ID | <51f334eb$0$29971$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #51215 |
As requested, some constructive criticism of your module.
On Thu, 25 Jul 2013 09:24:30 -0400, Devyn Collier Johnson wrote:
> #!/usr/bin/python3
> #Made by Devyn Collier Johnson, NCLA, Linux+, LPIC-1, DCTS
What's NCLA, Linux+, LPIC-1, DCTS? Do these mean anything? Are we
supposed to know what they mean?
"Made by" has no legal significance. You probably want:
Copyright © 2013 Devyn Collier Johnson.
> #Made using the Geany IDE
Nobody gives a monkey's toss what editor you used to type up the module.
You might as well mention the brand of monitor you used, or whether the
keyboard is Dvorak or Qwerty.
> #LGPLv3
You can't just drop in a mention of "LGPLv3" and expect it to mean
anything. You actually have to obey the licence yourself, and that
includes *actually including the licence in your work*. (You're
technically in violation of the licence at the moment, however since the
only person whose copyright you are infringing is yourself, it doesn't
matter. However anyone else using your code is at risk.)
http://www.gnu.org/licenses/gpl-howto.html
In the case of the LGPL, you have to include the text of *both* the GPL
and the LGPL, not just one.
> import re, sys, subprocess, platform
> def grep(regex,textf):
> #Sample Command: grep.grep("^x",dir()) #Syntax:
> boash.grep(regexp_string,list_of_strings_to_search)
Comments using # are only of use to people reading the source code. If
you want comments to be available at the interactive prompt, you should
write them as doc strings:
def grep(regex, textf):
"""This string is a docstring.
Sample command: ...
Blah blah blah
"""
Then, at the interactive prompt, the user can say:
help(boash.grep)
to read the docstring.
> version = '0.2a'
That's quite useless, since it is a local variable invisible outside of
the function.
Also, why would you bother giving every individual function a version
number? That's rather pointless. The user cannot pick and choose function
A with version number 0.6 and function B with version number 0.7 if the
module provides versions 0.7 of both.
> expr = re.compile(regex)
> match = re.findall(expr, textf)
> if match != None:
> print(match)
When comparing with None, it is preferred to use "is" and "is not" rather
than equality tests.
> def ls():
> version = '0.3'
> print(subprocess.getoutput('ls'))
> def dir():
> version = '0.3'
> print(subprocess.getoutput('dir'))
A blank line or two between functions does wonders for readability. There
is no prize for conserving newlines.
You might like to read PEP 8, the Python style guide. It is optional, but
still makes a very good guide.
http://www.python.org/dev/peps/pep-0008/
> def bash(*arg):
> version = '0.3'
> print(subprocess.getoutput(arg))
> def shell(*arg):
> version = '0.3'
> print(subprocess.getoutput(arg))
bash is not a synonym for "shell". "The shell" might be sh, csh, bash, or
any one of many other shells, all of which are slightly (or not so
slightly) different.
> def clear_bash_history():
> version = '0.3'
> print(subprocess.getoutput('history -c'))
[...]
Do you really need ten aliases for 'history -c'?
If you want to define aliases for a function, don't recreate the entire
function ten times. Start with defining the function once, then:
clear_bash_hist = clear_hist = clear_history = clear_bash_history
etc. But really, having ten names for the one function just confuses
people, who then wonder what subtle difference there is between
delete_history and clear_history.
> def firefox():
> version = '0.3'
> print(subprocess.Popen('(firefox &)'))
Is Firefox really so important that it needs a dedicated command?
What about Debian users? Doesn't Iceweasel get a command?
> def xterm():
> version = '0.3'
> print(subprocess.Popen('(xterm &)'))
Surely the user already has an xterm open, if they are running this
interactively? Why not just use your xterm's "new window" or "new tab"
command?
[...delete more trivial calls to subprocess...]
> def repeat_cmd():
> version = '0.3'
> print(subprocess.Popen('!!'))
[... delete two exact copies of this function...]
> def ejcd():
> version = '0.3'
> print(subprocess.Popen('eject cdrom1'))
[... delete FOURTEEN exact copies of this function...]
Really? Is anyone going to type "eject_disc_tray" instead of "eject"?
I think that will do.
This doesn't really do anything except define a large number of trivial
wrappers to commands already available in the shell. Emphasis on the
*trivial* -- with the exception of the grep wrapper, which is all of four
lines (ignoring the useless internal version number), every single one of
these wrapper functions is a one-liner.[1] In other words, you're not
adding any value to the shell commands by wrapping them in Python. There
are plenty of big, complex shell commands that take a plethora of options
and could do with some useful Python wrappers, like wget. But you haven't
done them.
Nor have you added extra security, or even extra convenience. You've done
nothing that couldn't be done using the shell "alias" command, except in
Python where the syntax is less convenient (e.g. "ls" in the shell,
versus "ls()" in Python).
[1] I think every newbie programmer goes through a stage of pointlessly
writing one-liner wrappers to every second function they see. I know I
did. The difference is, before the Internet, nobody did it publicly.
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| Date | 2013-07-27 08:56 -0400 |
| Message-ID | <mailman.5167.1374929783.3114.python-list@python.org> |
| In reply to | #51329 |
On 07/26/2013 10:48 PM, Steven D'Aprano wrote:
> As requested, some constructive criticism of your module.
>
> On Thu, 25 Jul 2013 09:24:30 -0400, Devyn Collier Johnson wrote:
>
>> #!/usr/bin/python3
>> #Made by Devyn Collier Johnson, NCLA, Linux+, LPIC-1, DCTS
> What's NCLA, Linux+, LPIC-1, DCTS? Do these mean anything? Are we
> supposed to know what they mean?
>
> "Made by" has no legal significance. You probably want:
>
> Copyright © 2013 Devyn Collier Johnson.
>
>
>> #Made using the Geany IDE
> Nobody gives a monkey's toss what editor you used to type up the module.
> You might as well mention the brand of monitor you used, or whether the
> keyboard is Dvorak or Qwerty.
>
>
>> #LGPLv3
> You can't just drop in a mention of "LGPLv3" and expect it to mean
> anything. You actually have to obey the licence yourself, and that
> includes *actually including the licence in your work*. (You're
> technically in violation of the licence at the moment, however since the
> only person whose copyright you are infringing is yourself, it doesn't
> matter. However anyone else using your code is at risk.)
>
> http://www.gnu.org/licenses/gpl-howto.html
>
> In the case of the LGPL, you have to include the text of *both* the GPL
> and the LGPL, not just one.
>
>
>
>> import re, sys, subprocess, platform
>> def grep(regex,textf):
>> #Sample Command: grep.grep("^x",dir()) #Syntax:
>> boash.grep(regexp_string,list_of_strings_to_search)
> Comments using # are only of use to people reading the source code. If
> you want comments to be available at the interactive prompt, you should
> write them as doc strings:
>
> def grep(regex, textf):
> """This string is a docstring.
>
> Sample command: ...
> Blah blah blah
> """
>
> Then, at the interactive prompt, the user can say:
>
> help(boash.grep)
>
> to read the docstring.
>
>
>> version = '0.2a'
> That's quite useless, since it is a local variable invisible outside of
> the function.
>
> Also, why would you bother giving every individual function a version
> number? That's rather pointless. The user cannot pick and choose function
> A with version number 0.6 and function B with version number 0.7 if the
> module provides versions 0.7 of both.
>
>
>> expr = re.compile(regex)
>> match = re.findall(expr, textf)
>> if match != None:
>> print(match)
> When comparing with None, it is preferred to use "is" and "is not" rather
> than equality tests.
>
>
>> def ls():
>> version = '0.3'
>> print(subprocess.getoutput('ls'))
>> def dir():
>> version = '0.3'
>> print(subprocess.getoutput('dir'))
> A blank line or two between functions does wonders for readability. There
> is no prize for conserving newlines.
>
> You might like to read PEP 8, the Python style guide. It is optional, but
> still makes a very good guide.
>
> http://www.python.org/dev/peps/pep-0008/
>
>
>> def bash(*arg):
>> version = '0.3'
>> print(subprocess.getoutput(arg))
>> def shell(*arg):
>> version = '0.3'
>> print(subprocess.getoutput(arg))
> bash is not a synonym for "shell". "The shell" might be sh, csh, bash, or
> any one of many other shells, all of which are slightly (or not so
> slightly) different.
>
>
>> def clear_bash_history():
>> version = '0.3'
>> print(subprocess.getoutput('history -c'))
> [...]
>
> Do you really need ten aliases for 'history -c'?
>
> If you want to define aliases for a function, don't recreate the entire
> function ten times. Start with defining the function once, then:
>
> clear_bash_hist = clear_hist = clear_history = clear_bash_history
>
> etc. But really, having ten names for the one function just confuses
> people, who then wonder what subtle difference there is between
> delete_history and clear_history.
>
>> def firefox():
>> version = '0.3'
>> print(subprocess.Popen('(firefox &)'))
> Is Firefox really so important that it needs a dedicated command?
>
> What about Debian users? Doesn't Iceweasel get a command?
>
>
>> def xterm():
>> version = '0.3'
>> print(subprocess.Popen('(xterm &)'))
> Surely the user already has an xterm open, if they are running this
> interactively? Why not just use your xterm's "new window" or "new tab"
> command?
>
>
> [...delete more trivial calls to subprocess...]
>
>> def repeat_cmd():
>> version = '0.3'
>> print(subprocess.Popen('!!'))
> [... delete two exact copies of this function...]
>
>> def ejcd():
>> version = '0.3'
>> print(subprocess.Popen('eject cdrom1'))
> [... delete FOURTEEN exact copies of this function...]
>
> Really? Is anyone going to type "eject_disc_tray" instead of "eject"?
>
>
> I think that will do.
>
> This doesn't really do anything except define a large number of trivial
> wrappers to commands already available in the shell. Emphasis on the
> *trivial* -- with the exception of the grep wrapper, which is all of four
> lines (ignoring the useless internal version number), every single one of
> these wrapper functions is a one-liner.[1] In other words, you're not
> adding any value to the shell commands by wrapping them in Python. There
> are plenty of big, complex shell commands that take a plethora of options
> and could do with some useful Python wrappers, like wget. But you haven't
> done them.
>
> Nor have you added extra security, or even extra convenience. You've done
> nothing that couldn't be done using the shell "alias" command, except in
> Python where the syntax is less convenient (e.g. "ls" in the shell,
> versus "ls()" in Python).
>
>
>
>
> [1] I think every newbie programmer goes through a stage of pointlessly
> writing one-liner wrappers to every second function they see. I know I
> did. The difference is, before the Internet, nobody did it publicly.
>
>
Wow! Thanks for the thorough critic. I appreciate your feed back and
thank you so much for the PEP link. I learned a lot. I never saw that
page before.
The "NCLA, Linux+, LPIC-1, DCTS" are my computer certifications. As for
mentioning Geany, I am trying to promote and give credit to Geany.
Good point about the Made by/Copyright suggestion. Although, I have not
copyrighted the file, can I still say "Copyrighted by ...". Thank you
for the LGPLv3 suggestion. I know that I must include the GPL license
for GPL programs, but I thought for LGPL code I could just have
"#LGPLv3". Thank you so much for that feedback. I definitely need to
read about all of the types of licenses.
I thought it would be helpful to include the version numbers for each
function, but you and another Python developer said it is pointless. I
see what you mean.
The grep emulating function does not work yet. I am still working on that.
Yeah, I have a VERY BAD habit of treating bash and the Linux shell (or
any/all shells) as the same thing. I know they are all very different,
but for some reason I still keep calling all shells in general BASH.
I will be sure to create aliases. I make alias commands so that it is
easier to guess or remember a command. For instance, a Python user my
want to clear the shell's history, but can only remember one form of the
command or must guess. On my Ubuntu system, I have set up numerous shell
aliases. I am addicted to aliases.
I still need to add the other browsers. Do very many people use Iceweasel?
I did not notice that I have "print(subprocess.Popen('(xterm &)'))"
instead of "subprocess.Popen('(xterm &)')". The worst computer errors
are ID-10-T errors.
True, the user my have Xterm open, but what if they use Guake (like me)
or Pterm, EvilVTE, Valaterm, Gnome-Terminal, Konsole, etc.?
How could I add security and convenience? Okay, I will try to add wget.
Are there any other shell commands that anyone feels I should add?
The point of this module is to allow Linux shell users to use Python3 as
a regular shell. Instead of using CSH, Bash, Tcsh, FISH, etc., users
could use Python3 and import this module. Python is more powerful than
any shell, so I want to make it easier for anyone to use Python as the
default shell. For instance, instead of typing "print(os.getcwd())" to
get the current working directory, users could type "boash.ls()". I hope
that is easier to remember than "print(os.getcwd())". As for the print()
command, I do not like how os.getcwd() has single quotes around the
output. Plus, Linux shell do not print output with quotes.
I want to make this a very useful and popular module, so I will use the
suggestions and add more useful wrappers. Would it help if I made a
Youtube video showing how this module can be used?
I will post the next version on this mailing list for another review.
Thanks everyone, and thanks a lot Steven D'Aprano!
Mahalo,
Devyn Collier Johnson
DevynCJohnson@Gmail.com
[toc] | [prev] | [next] | [standalone]
| From | Alister <alister.ware@ntlworld.com> |
|---|---|
| Date | 2013-07-27 16:32 +0000 |
| Message-ID | <GCSIt.19002$zk4.10612@fx18.am4> |
| In reply to | #51352 |
On Sat, 27 Jul 2013 08:56:10 -0400, Devyn Collier Johnson wrote: > > Good point about the Made by/Copyright suggestion. Although, I have not > copyrighted the file, can I still say "Copyrighted by ...".-- There is no special process to Copyright anything. the simple act of writing it automatically gives you the copyright on your own work. Proving that it was you that created the work & when may be a little trickier if you do not find a reliable means of recording the event though. (posting to this news group actually gives a reasonable time stamp provided the article has not expired by the time it is needed) Stenderup's Law: The sooner you fall behind, the more time you will have to catch up.
[toc] | [prev] | [next] | [standalone]
| From | Dave Angel <davea@davea.name> |
|---|---|
| Date | 2013-07-27 12:58 -0400 |
| Message-ID | <mailman.5181.1374944307.3114.python-list@python.org> |
| In reply to | #51367 |
On 07/27/2013 12:32 PM, Alister wrote:
> On Sat, 27 Jul 2013 08:56:10 -0400, Devyn Collier Johnson wrote:
>>
>> Good point about the Made by/Copyright suggestion. Although, I have not
>> copyrighted the file, can I still say "Copyrighted by ...".--
>
> There is no special process to Copyright anything.
> the simple act of writing it automatically gives you the copyright on
> your own work.
>
> Proving that it was you that created the work & when may be a little
> trickier if you do not find a reliable means of recording the event
> though.
> (posting to this news group actually gives a reasonable time stamp
> provided the article has not expired by the time it is needed)
>
The copyright law varies by country, and it's wise to look up your own
country's rules, as well as investigate international law. The
following is based only on my limited recollection of US law. I include
references below, but have not re-studied them. Nor am I a lawyer.
Generally, a copyright does belong to the author, as soon as he provably
commits the text to medium. However, if you ever expect to defend a
copyright, it can be useful to register it. Registration is limited by
law to certain time limits. I don't recall exactly, but I believe that
once something is published, it has to be registered within 3 months or so.
Registration is easy, but not free. You can copyright a number of works
simultaneously, but I think they have to be of the same type. And if it
is registered, you can sue for larger amounts, and you'll have a much
easier time getting a lawyer to take the case for you.
See: http://www.copyright.gov/
and especially:
http://www.copyright.gov/circs/circ01.pdf
--
DaveA
[toc] | [prev] | [next] | [standalone]
| From | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| Date | 2013-07-27 09:19 -0400 |
| Message-ID | <mailman.5168.1374931208.3114.python-list@python.org> |
| In reply to | #51329 |
On 07/26/2013 10:48 PM, Steven D'Aprano wrote:
> As requested, some constructive criticism of your module.
>
> On Thu, 25 Jul 2013 09:24:30 -0400, Devyn Collier Johnson wrote:
>
>> #!/usr/bin/python3
>> #Made by Devyn Collier Johnson, NCLA, Linux+, LPIC-1, DCTS
> What's NCLA, Linux+, LPIC-1, DCTS? Do these mean anything? Are we
> supposed to know what they mean?
>
> "Made by" has no legal significance. You probably want:
>
> Copyright © 2013 Devyn Collier Johnson.
>
>
>> #Made using the Geany IDE
> Nobody gives a monkey's toss what editor you used to type up the module.
> You might as well mention the brand of monitor you used, or whether the
> keyboard is Dvorak or Qwerty.
>
>
>> #LGPLv3
> You can't just drop in a mention of "LGPLv3" and expect it to mean
> anything. You actually have to obey the licence yourself, and that
> includes *actually including the licence in your work*. (You're
> technically in violation of the licence at the moment, however since the
> only person whose copyright you are infringing is yourself, it doesn't
> matter. However anyone else using your code is at risk.)
>
> http://www.gnu.org/licenses/gpl-howto.html
>
> In the case of the LGPL, you have to include the text of *both* the GPL
> and the LGPL, not just one.
>
>
>
>> import re, sys, subprocess, platform
>> def grep(regex,textf):
>> #Sample Command: grep.grep("^x",dir()) #Syntax:
>> boash.grep(regexp_string,list_of_strings_to_search)
> Comments using # are only of use to people reading the source code. If
> you want comments to be available at the interactive prompt, you should
> write them as doc strings:
>
> def grep(regex, textf):
> """This string is a docstring.
>
> Sample command: ...
> Blah blah blah
> """
>
> Then, at the interactive prompt, the user can say:
>
> help(boash.grep)
>
> to read the docstring.
>
>
>> version = '0.2a'
> That's quite useless, since it is a local variable invisible outside of
> the function.
>
> Also, why would you bother giving every individual function a version
> number? That's rather pointless. The user cannot pick and choose function
> A with version number 0.6 and function B with version number 0.7 if the
> module provides versions 0.7 of both.
>
>
>> expr = re.compile(regex)
>> match = re.findall(expr, textf)
>> if match != None:
>> print(match)
> When comparing with None, it is preferred to use "is" and "is not" rather
> than equality tests.
>
>
>> def ls():
>> version = '0.3'
>> print(subprocess.getoutput('ls'))
>> def dir():
>> version = '0.3'
>> print(subprocess.getoutput('dir'))
> A blank line or two between functions does wonders for readability. There
> is no prize for conserving newlines.
>
> You might like to read PEP 8, the Python style guide. It is optional, but
> still makes a very good guide.
>
> http://www.python.org/dev/peps/pep-0008/
>
>
>> def bash(*arg):
>> version = '0.3'
>> print(subprocess.getoutput(arg))
>> def shell(*arg):
>> version = '0.3'
>> print(subprocess.getoutput(arg))
> bash is not a synonym for "shell". "The shell" might be sh, csh, bash, or
> any one of many other shells, all of which are slightly (or not so
> slightly) different.
>
>
>> def clear_bash_history():
>> version = '0.3'
>> print(subprocess.getoutput('history -c'))
> [...]
>
> Do you really need ten aliases for 'history -c'?
>
> If you want to define aliases for a function, don't recreate the entire
> function ten times. Start with defining the function once, then:
>
> clear_bash_hist = clear_hist = clear_history = clear_bash_history
>
> etc. But really, having ten names for the one function just confuses
> people, who then wonder what subtle difference there is between
> delete_history and clear_history.
>
>> def firefox():
>> version = '0.3'
>> print(subprocess.Popen('(firefox &)'))
> Is Firefox really so important that it needs a dedicated command?
>
> What about Debian users? Doesn't Iceweasel get a command?
>
>
>> def xterm():
>> version = '0.3'
>> print(subprocess.Popen('(xterm &)'))
> Surely the user already has an xterm open, if they are running this
> interactively? Why not just use your xterm's "new window" or "new tab"
> command?
>
>
> [...delete more trivial calls to subprocess...]
>
>> def repeat_cmd():
>> version = '0.3'
>> print(subprocess.Popen('!!'))
> [... delete two exact copies of this function...]
>
>> def ejcd():
>> version = '0.3'
>> print(subprocess.Popen('eject cdrom1'))
> [... delete FOURTEEN exact copies of this function...]
>
> Really? Is anyone going to type "eject_disc_tray" instead of "eject"?
>
>
> I think that will do.
>
> This doesn't really do anything except define a large number of trivial
> wrappers to commands already available in the shell. Emphasis on the
> *trivial* -- with the exception of the grep wrapper, which is all of four
> lines (ignoring the useless internal version number), every single one of
> these wrapper functions is a one-liner.[1] In other words, you're not
> adding any value to the shell commands by wrapping them in Python. There
> are plenty of big, complex shell commands that take a plethora of options
> and could do with some useful Python wrappers, like wget. But you haven't
> done them.
>
> Nor have you added extra security, or even extra convenience. You've done
> nothing that couldn't be done using the shell "alias" command, except in
> Python where the syntax is less convenient (e.g. "ls" in the shell,
> versus "ls()" in Python).
>
>
>
>
> [1] I think every newbie programmer goes through a stage of pointlessly
> writing one-liner wrappers to every second function they see. I know I
> did. The difference is, before the Internet, nobody did it publicly.
>
>
About the aliases, I have tried setting pwd() as an alias for
"os.getcwd()", but I cannot type "pwd()" and get the desired output.
Instead, I must type "pwd". I tested this in Guake running Python3.3.
>>> os.getcwd()
'/home/collier'
>>> pwd = os.getcwd()
>>> pwd()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> pwd
'/home/collier'
>>> pwd() = os.getcwd()
File "<stdin>", line 1
SyntaxError: can't assign to function call
How could I make pwd() work?
Mahalo,
DCJ
[toc] | [prev] | [next] | [standalone]
| From | Dave Angel <davea@davea.name> |
|---|---|
| Date | 2013-07-27 09:35 -0400 |
| Message-ID | <mailman.5169.1374932171.3114.python-list@python.org> |
| In reply to | #51329 |
On 07/27/2013 09:19 AM, Devyn Collier Johnson wrote:
>
<SNIP>
> About the aliases, I have tried setting pwd() as an alias for
> "os.getcwd()", but I cannot type "pwd()" and get the desired output.
> Instead, I must type "pwd". I tested this in Guake running Python3.3.
>
> >>> os.getcwd()
> '/home/collier'
> >>> pwd = os.getcwd()
> >>> pwd()
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: 'str' object is not callable
> >>> pwd
> '/home/collier'
> >>> pwd() = os.getcwd()
> File "<stdin>", line 1
> SyntaxError: can't assign to function call
>
>
> How could I make pwd() work?
Don't call getcwd() when making the alias. You want it to be called
when USING the alias.
pwd = os.getcwd #creates the alias
pwd() #runs the alias
--
DaveA
[toc] | [prev] | [next] | [standalone]
| From | Chris “Kwpolska” Warrick <kwpolska@gmail.com> |
|---|---|
| Date | 2013-07-27 15:36 +0200 |
| Message-ID | <mailman.5170.1374932207.3114.python-list@python.org> |
| In reply to | #51329 |
On Sat, Jul 27, 2013 at 3:19 PM, Devyn Collier Johnson
<devyncjohnson@gmail.com> wrote:
> About the aliases, I have tried setting pwd() as an alias for "os.getcwd()",
> but I cannot type "pwd()" and get the desired output. Instead, I must type
> "pwd". I tested this in Guake running Python3.3.
>
>>>> os.getcwd()
> '/home/collier'
>>>> pwd = os.getcwd()
>>>> pwd()
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: 'str' object is not callable
>>>> pwd
> '/home/collier'
>>>> pwd() = os.getcwd()
> File "<stdin>", line 1
> SyntaxError: can't assign to function call
>
>
> How could I make pwd() work?
>
>
> Mahalo,
>
> DCJ
> --
> http://mail.python.org/mailman/listinfo/python-list
>>> import os
>>> pwd = os.getcwd
>>> pwd()
'/home/kwpolska'
>>> os.chdir('/')
>>> pwd()
'/'
>>>
--
Chris “Kwpolska” Warrick <http://kwpolska.tk>
PGP: 5EAAEA16
stop html mail | always bottom-post | only UTF-8 makes sense
[toc] | [prev] | [next] | [standalone]
| From | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| Date | 2013-07-27 09:44 -0400 |
| Message-ID | <mailman.5172.1374932681.3114.python-list@python.org> |
| In reply to | #51329 |
On 07/27/2013 09:35 AM, Dave Angel wrote: > On 07/27/2013 09:19 AM, Devyn Collier Johnson wrote: >> > > <SNIP> > >> About the aliases, I have tried setting pwd() as an alias for >> "os.getcwd()", but I cannot type "pwd()" and get the desired output. >> Instead, I must type "pwd". I tested this in Guake running Python3.3. >> >> >>> os.getcwd() >> '/home/collier' >> >>> pwd = os.getcwd() >> >>> pwd() >> Traceback (most recent call last): >> File "<stdin>", line 1, in <module> >> TypeError: 'str' object is not callable >> >>> pwd >> '/home/collier' >> >>> pwd() = os.getcwd() >> File "<stdin>", line 1 >> SyntaxError: can't assign to function call >> >> >> How could I make pwd() work? > > Don't call getcwd() when making the alias. You want it to be called > when USING the alias. > > pwd = os.getcwd #creates the alias > > pwd() #runs the alias > > > Thanks! It works! >>> pwd = os.getcwd >>> pwd() '/home/collier' Mahalo, Dave! DCJ
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2013-07-27 14:37 +0100 |
| Message-ID | <mailman.5173.1374934020.3114.python-list@python.org> |
| In reply to | #51329 |
On Sat, Jul 27, 2013 at 2:19 PM, Devyn Collier Johnson <devyncjohnson@gmail.com> wrote: > About the aliases, I have tried setting pwd() as an alias for "os.getcwd()", > but I cannot type "pwd()" and get the desired output. Instead, I must type > "pwd". I tested this in Guake running Python3.3. > >>>> os.getcwd() > '/home/collier' >>>> pwd = os.getcwd() >>>> pwd() Try: >>> pwd = os.getcwd Otherwise you're calling it immediately. ChrisA
[toc] | [prev] | [next] | [standalone]
| From | Dave Angel <davea@davea.name> |
|---|---|
| Date | 2013-07-27 10:33 -0400 |
| Message-ID | <mailman.5174.1374935628.3114.python-list@python.org> |
| In reply to | #51329 |
On 07/27/2013 08:56 AM, Devyn Collier Johnson wrote:
>
<SNIP>
Somehow during this thread, you have changed your purpose for this
library. It used to be a library that Python programmers could import
and use. And now, it's a shell replacement? The user runs the Python
interpreter, and types the line import boash to get started.
>
> Yeah, I have a VERY BAD habit of treating bash and the Linux shell (or
> any/all shells) as the same thing. I know they are all very different,
> but for some reason I still keep calling all shells in general BASH.
I seem to recall that BASH is an acronym, for Bourne Again SHell.
>
> I will be sure to create aliases. I make alias commands so that it is
> easier to guess or remember a command. For instance, a Python user my
> want to clear the shell's history, but can only remember one form of the
> command or must guess. On my Ubuntu system, I have set up numerous shell
> aliases. I am addicted to aliases.
Nothing wrong with aliases. But how does your user create his own
aliases? That's much more important than having yours available.
Remember that any new globals he defines are lost when he exits the
interpreter (or crashes).
>
> I still need to add the other browsers. Do very many people use Iceweasel?
>
> I did not notice that I have "print(subprocess.Popen('(xterm &)'))"
> instead of "subprocess.Popen('(xterm &)')". The worst computer errors
> are ID-10-T errors.
>
> True, the user my have Xterm open, but what if they use Guake (like me)
> or Pterm, EvilVTE, Valaterm, Gnome-Terminal, Konsole, etc.?
Exactly. If they're using a terminal with tabs, they might want to
create a new tab, not a new instance of the terminal. Or if they're
running screen or the equivalent, they want the new terminal to show up
on their (remote) console.
>
> How could I add security and convenience? Okay, I will try to add wget.
> Are there any other shell commands that anyone feels I should add?
How about tab? The command-completion and filename-completion and
parameter-completion logic of a good shell is extremely complex, and
very useful.
>
> The point of this module is to allow Linux shell users to use Python3 as
> a regular shell. Instead of using CSH, Bash, Tcsh, FISH, etc., users
> could use Python3 and import this module. Python is more powerful than
> any shell, so I want to make it easier for anyone to use Python as the
> default shell. For instance, instead of typing "print(os.getcwd())" to
> get the current working directory, users could type "boash.ls()". I hope
> that is easier to remember than "print(os.getcwd())".
It's easier, but doesn't begin to do the same thing.
As for using this INSTEAD of csh, bash, etc., that might be likely once
it gets beyond 10% of the usefulness. Right now, it's at about 0.01%
And any habits a user gets within this environment have to be unlearned
when he returns to a standard shell.
Back in the early days of MSDOS, the FORMAT command didn't require you
to specify a drive letter. So people could intend to format their
floppy, and actually trash their hard disk. So I had colleagues who put
a FORMAT.BAt command on their path which hard-wired the A: parameter.
Now what happens to one of those folks when he goes to another machine
which doesn't have that batch file? Ouch! Instead I wrote a batch file
that checked to make sure you had the A: parameter. Rapidly, my fingers
learned that FORMAT A: was the valid syntax, and pretty soon the batch
file was unnecessary (for me).
If I were going to define a dozen aliases for other people to use, I'd
make them something like:
def clear_hist():
print "The function you want is probably clear_history()"
> As for the print()
> command, I do not like how os.getcwd() has single quotes around the
> output.
Those quotes come from the Python interpreter, not from getcwd().
> Plus, Linux shell do not print output with quotes.
>
> I want to make this a very useful and popular module, so I will use the
> suggestions and add more useful wrappers. Would it help if I made a
> Youtube video showing how this module can be used?
>
> I will post the next version on this mailing list for another review.
> Thanks everyone, and thanks a lot Steven D'Aprano!
>
Have you figured out how you're going to do things like cd (os.chdir),
which have to remember state? And how to pass the new current directory
to the shell that launched Python?
Have you looked at ipython (ipython.org) ? At least from there, you can
get command completion with tab, one-third of the bash functionality.
So if you type boash.cle then <tab> it'll fill in the rest.
--
DaveA
[toc] | [prev] | [next] | [standalone]
| From | Devyn Collier Johnson <devyncjohnson@gmail.com> |
|---|---|
| Date | 2013-07-27 10:53 -0400 |
| Message-ID | <mailman.5175.1374936812.3114.python-list@python.org> |
| In reply to | #51329 |
On 07/27/2013 10:33 AM, Dave Angel wrote:
> On 07/27/2013 08:56 AM, Devyn Collier Johnson wrote:
>>
>
> <SNIP>
>
> Somehow during this thread, you have changed your purpose for this
> library. It used to be a library that Python programmers could import
> and use. And now, it's a shell replacement? The user runs the Python
> interpreter, and types the line import boash to get started.
>
>>
>> Yeah, I have a VERY BAD habit of treating bash and the Linux shell (or
>> any/all shells) as the same thing. I know they are all very different,
>> but for some reason I still keep calling all shells in general BASH.
>
> I seem to recall that BASH is an acronym, for Bourne Again SHell.
>
>>
>> I will be sure to create aliases. I make alias commands so that it is
>> easier to guess or remember a command. For instance, a Python user my
>> want to clear the shell's history, but can only remember one form of the
>> command or must guess. On my Ubuntu system, I have set up numerous shell
>> aliases. I am addicted to aliases.
>
> Nothing wrong with aliases. But how does your user create his own
> aliases? That's much more important than having yours available.
> Remember that any new globals he defines are lost when he exits the
> interpreter (or crashes).
>
>>
>> I still need to add the other browsers. Do very many people use
>> Iceweasel?
>>
>> I did not notice that I have "print(subprocess.Popen('(xterm &)'))"
>> instead of "subprocess.Popen('(xterm &)')". The worst computer errors
>> are ID-10-T errors.
>>
>> True, the user my have Xterm open, but what if they use Guake (like me)
>> or Pterm, EvilVTE, Valaterm, Gnome-Terminal, Konsole, etc.?
>
> Exactly. If they're using a terminal with tabs, they might want to
> create a new tab, not a new instance of the terminal. Or if they're
> running screen or the equivalent, they want the new terminal to show
> up on their (remote) console.
>
>>
>> How could I add security and convenience? Okay, I will try to add wget.
>> Are there any other shell commands that anyone feels I should add?
>
> How about tab? The command-completion and filename-completion and
> parameter-completion logic of a good shell is extremely complex, and
> very useful.
>
>>
>> The point of this module is to allow Linux shell users to use Python3 as
>> a regular shell. Instead of using CSH, Bash, Tcsh, FISH, etc., users
>> could use Python3 and import this module. Python is more powerful than
>> any shell, so I want to make it easier for anyone to use Python as the
>> default shell. For instance, instead of typing "print(os.getcwd())" to
>> get the current working directory, users could type "boash.ls()". I hope
>> that is easier to remember than "print(os.getcwd())".
>
> It's easier, but doesn't begin to do the same thing.
>
> As for using this INSTEAD of csh, bash, etc., that might be likely
> once it gets beyond 10% of the usefulness. Right now, it's at about
> 0.01% And any habits a user gets within this environment have to be
> unlearned when he returns to a standard shell.
>
> Back in the early days of MSDOS, the FORMAT command didn't require you
> to specify a drive letter. So people could intend to format their
> floppy, and actually trash their hard disk. So I had colleagues who
> put a FORMAT.BAt command on their path which hard-wired the A:
> parameter. Now what happens to one of those folks when he goes to
> another machine which doesn't have that batch file? Ouch! Instead I
> wrote a batch file that checked to make sure you had the A:
> parameter. Rapidly, my fingers learned that FORMAT A: was the valid
> syntax, and pretty soon the batch file was unnecessary (for me).
>
> If I were going to define a dozen aliases for other people to use, I'd
> make them something like:
>
> def clear_hist():
> print "The function you want is probably clear_history()"
>
>
>> As for the print()
>> command, I do not like how os.getcwd() has single quotes around the
>> output.
>
> Those quotes come from the Python interpreter, not from getcwd().
>
>> Plus, Linux shell do not print output with quotes.
>>
>> I want to make this a very useful and popular module, so I will use the
>> suggestions and add more useful wrappers. Would it help if I made a
>> Youtube video showing how this module can be used?
>>
>> I will post the next version on this mailing list for another review.
>> Thanks everyone, and thanks a lot Steven D'Aprano!
>>
>
> Have you figured out how you're going to do things like cd (os.chdir),
> which have to remember state? And how to pass the new current
> directory to the shell that launched Python?
>
> Have you looked at ipython (ipython.org) ? At least from there, you
> can get command completion with tab, one-third of the bash
> functionality. So if you type boash.cle then <tab> it'll fill in
> the rest.
>
>
Thanks! I will look into IPython. I am familiar with it already. Yes, I
have two purposes for the module, but after reading these suggestions I
have modified my goal and purpose to achieve the goal of making a useful
and popular Python3 module. The whole point of my boash project is to
make a useful module. How can I make this module useful? I am fixing the
problems and implementing suggestions.
Would a Python3 game module be more useful? I plan to make a function
that rolls a die and prints the output (You got a 5) and other similar
random games.
Mahalo,
DCJ
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2013-07-27 16:15 +0100 |
| Message-ID | <mailman.5177.1374938159.3114.python-list@python.org> |
| In reply to | #51329 |
On Sat, Jul 27, 2013 at 3:53 PM, Devyn Collier Johnson <devyncjohnson@gmail.com> wrote: > Would a Python3 game module be more useful? I plan to make a function that > rolls a die and prints the output (You got a 5) and other similar random > games. Taking someone else's module and learning to use it has a cost. Plus there's licensing and other issues (if you release your library GPL3, you force anyone who uses it to do the same - though I'm not 100% sure how that goes with Python modules, since they're not 'linked' the way others are), not to mention the time spent finding out that your module even exists. For a module to be useful, all those costs combined have to be lower than the cost of just writing the code yourself when you need it. On the other hand, it's VERY common for a programmer to have his own *personal* utilities module. Stuff stuff in there whenever you think it'll be useful, import it into your applications, et voila. The bar is way lower for that. Your dice-roller is perhaps useful to yourself, without being worth the effort for someone else to learn. Plus, you get to decide exactly how much flexibility you need. Do you only ever need to roll a single six-sider at a time? Then don't bother implementing stuff like I did for Minstrel Hall, where we play Dungeons and Dragons: [ROLL] Rosuav (Gaston crit dmg) rolls 4d8: 7, 3, 6, 1, totalling 17. [ROLL] Rosuav (Gaston crit dmg) rolls d6: 1 (elec) [ROLL] Rosuav (Gaston crit dmg) rolls d10: 6 (burst) [ROLL] Rosuav (Gaston crit dmg) rolls d8: 8 (thunder) [ROLL] For 4d8+12 STR+10 ench+4 specialization+d6 elec+d10 burst+d8 thunder+20 PA, Rosuav (Gaston crit dmg) totals: 78 Okay, that's a somewhat extreme example, but it's common to roll damage as, say, 2d6+13, which means two six-sided dice plus a constant 13. (This will result in damage between 15 and 25, with 20 being significantly more likely than either of the extremes.) And even that is probably a lot more complicated than you'll need for your purposes... yet for a D&D system, a dice roller that can only do a single d6 at a time is utterly useless. There's actually a dice roller module on PyPI already [1]; and it's probably of no use to you, because it's as complicated as I described above. I personally wouldn't use it, though, because I can't see a license - which comes back to the issues I listed above. Again, not an issue for your own code; if it's your copyright, you can do with it as you wish. [1] https://pypi.python.org/pypi/diceroll ChrisA
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web