Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Simple indentation. Does it need improvement?

by siberia-man (Friar)
on Dec 12, 2017 at 20:07 UTC ( [id://1205367]=perlquestion: print w/replies, xml ) Need Help??

siberia-man has asked for the wisdom of the Perl Monks concerning the following question:

Hi all again, Some time ago I came back to Perl world. Today I'd like to discuss some stuff. It has to do with output formatting, generation of the indentation literally. I tried to implement the module the simple as much as possible. of course, I know there is a lot of modules with the same functionality (for example, Text::Indent, Print::Indented or String::Indent). Most probably, those modules more powerful and more flexible than mine. But I needed:

-- simplicity

-- easiness

-- immediate effect

Completing this module, I guess achieved the main goals. It is quite easy in use, clear and effective. It doesn't support buffering or formatting to a string and doesn't support printing to other streams.

Example of usage:
# use 4 spaces indents and automatic line ending use indent indent_size => 4, indent_eol => 1; indent::new; indent::printf "Hello, world!"; indent::new; indent::printf "Hello, world!"; indent::back; indent::printf "Hello, world!"; indent::back;

The resulting output is as follows:
Hello, world! Hello, world! Hello, world!

The full source of the module is below:
package indent; use strict; use warnings; our $INDENT = " " x 4; our $EOL = $\; sub import { shift; goto &config; } sub config { my %opts = @_; if ( $opts{indent_tab} ) { $INDENT = "\t"; } elsif ( defined $opts{indent_size} && $opts{indent_size} =~ /^\d+$/ ) { $INDENT = " " x $opts{indent_size}; } if ( defined $opts{indent_eol} ) { $EOL = $opts{indent_eol} ? "\n" : $\; } } my @indent = ( "" ); sub reset { @indent = $indent[0]; } sub current { $indent[-1]; } sub new { push @indent, current . $INDENT; } sub back { pop @indent if @indent > 1; } sub print { local $\ = $EOL; CORE::print current, @_; } sub vprint { local $\ = $EOL; CORE::print current, $_ for ( @_ ); } sub printf { indent::print sprintf(( shift || "" ), @_); } 1;

Replies are listed 'Best First'.
Re: Simple indentation. Does it need improvement?
by haukex (Archbishop) on Dec 12, 2017 at 22:33 UTC

    This seems like something that would be ideal as an OO module, your subs could be converted to methods. This would also make the objects safe to mix, instead of simply having one "global" indent level.

    As for the code you posted, holli already pointed out that module names in lower case are by convention pragmas. Also, just a small note on ( shift || "" ): this means that if $_[0] is "0" or 0, it will be replaced by an empty string. You probably want the defined-or // instead, but it's usually fine to implement a printf replacement simply as sprintf(shift, @_).

    Since your post inspired me, here's my idea using overloading:

    use warnings; use strict; { package Indent; sub new { my $c=shift; bless { i=>shift//"\t", c=>0 }, $c } use overload '++' => sub { ++shift->{c} }, '--' => sub { my $s=shift; $$s{c}=0 if --$$s{c}<0; $$s{c} }, '""' => sub { my $s=shift; $$s{i} x $$s{c} }, '=' => sub { my $s=shift; bless {%$s}, ref $s }, } my $recurse; $recurse = sub { my ($lvl,$i) = @_ ? @_ : (0, Indent->new("foo")); print "<", $i++, "> Enter\n"; $recurse->($lvl+1,$i) unless $lvl>=2; print "<", --$i, "> Leave\n"; }; $recurse->(); __END__ <> Enter <foo> Enter <foofoo> Enter <foofoo> Leave <foo> Leave <> Leave
      Elegant approach. I've never thought in this direction. I thought about dynamic implementation but never took in my mind the idea of operator overloading. Please, give me chance to implement it as the next version of the updated module Indent. :)
Re: Simple indentation. Does it need improvement?
by holli (Abbot) on Dec 12, 2017 at 21:57 UTC
    • Module names should be capitalized, unless it's a pragma
    • A new-method should be reserved for constructors
    • Related to point 2), complementary methods should be named complementary (like start/end, open/close)


    holli

    You can lead your users to water, but alas, you cannot drown them.
      When I was reading the OP, the first thought I had was "POD". Why not to use the same names here? Indent::over, Indent::back, and maybe even more if needed?

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
      Thank you for your comments! Definitely I was in stuck inventing the name for opening the new indentation. I knew that "new" was reserved for constructor but couldn't give the proper name for this method. Thanks choroba as well. He gave me hint regarding Indent::over/Indent::back pair. Those are the best.

      Talking about lower case of the module name, I thought to use the module like a pragma: "use indent" to turn on new indentation and "no indent" to revert to previous indent level. I fastly recognized that there no ways for that and have left all my attempts in this direction. And the module name left without changes.

      I am going to take into account all the feedbacks by holli and choroba. What is the best way -- a) update the OP with the proper remarks or b) reply with new updated post?
Re: Simple indentation. Does it need improvement?
by Laurent_R (Canon) on Dec 12, 2017 at 22:38 UTC
    Hi siberia-man,

    I would advise you against using new in this context because it looks like an object constructor (and you module is not object-oriented).

    Below are some further comments after a very quick look at your code (but maybe I looked at it to quickly).

    If you made your module subroutine names a bit more specific, you could export them with a more limited risk of a name clash and then you could use them without having to prefix them with indent::. I would clearly be reluctant to use a module where I have to prefix all my sub calls with the module name. And, BTW, the module name should rather be Indent, with a leading capital letter. Do you really have to use the magical goto construct where, it seems to me, a simple function call would do?

    That's an implementation detail, but if I were to write such a module, I think I would rather implement the current indentation as a simple scalar variable recording the indentation level, e.g. 0 for no indentation, 1 for the first level, etc., and then just increment or decrement that variable when changing indentation level, rather than having an array of strings with push and pop operations. But that is really a detail, nobody really cares.

Re: Simple indentation. Does it need improvement?
by karlgoethebier (Abbot) on Dec 13, 2017 at 12:22 UTC

    I guess choroba (POD) and haukex (OO) are right.

    This little sketch does nothing, except that it compiles:

    package Siberia::Indent { use Role::Tiny; requires qw(beer); sub back { ... } sub over { ... } 1; } package Siberia::Acme { use Class::Tiny qw(beer); # whatever you need use Role::Tiny::With; with qw(Siberia::Indent); 1; } #!/usr/bin/env perl use strict; use warnings; use Siberia::Acme; use Try::Tiny; use feature qw(say); my $indent = Siberia::Acme->new( { beer => 2 } ); # say $indent->beer; try { $indent->back; } catch { say qq($_); }; try { $indent->over; } catch { say qq($_); }; __END__

    See also Class::Tiny and Role::Tiny.

    Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: Simple indentation. Does it need improvement?
by siberia-man (Friar) on Dec 17, 2017 at 19:52 UTC
    As I mentioned in the beginning: there are a lot of competitors implementing almost the same functionality. But all of their implementations don't follow my requirements -- to be simple, to be easy, to be flexible (but someone could say this is personal favor. May be...) That's why I have reinvented the wheel.

    And one question has arised. What is the best namespace for the module?

    Thank evevryone how was involved into this fruitful discussion. I have improved the module. Please have a look and give your feedback.

    The source code in the actual state is located on github by the link https://github.com/ildar-shaimordanov/indent-pm. Here I am posting the minimized version (no POD, no more descriptions, the bare code only and some examples of use cases).

    Example 1:
    use Indent( text => " ", eol => 1, level => 1 ); my @str = qw( down stairs to hell ); Indent::printf "@str"; ( Indent::over, Indent::printf $_ ) for ( @str ); Indent::reset; Indent::printf("there is no way");


    Example 2:
    use Indent; our $indent = Indent->new( text => " ", eol => 1, level => 1 ); local $\ = "\n"; my @str = qw( down stairs to hell ); print($indent, "@str"); { local $indent = $indent; ( ++$indent )->printf($_) for ( @str ); } print($indent - 1, "there is no way");


    Source code:
    package Indent; use strict; use warnings; # ==================================================================== +===== # Clamp a value on the edge, that is minimum. # So the value can't be less than this restriction. sub _lclamp { my ( $min, $v ) = @_; $v < $min ? $min : $v; } # Set the valid level and evaluate the proper indentation. sub _set_indent { my $p = shift; my $v = shift // 0; $p->{level} = _lclamp( 0, $p->{level} += $v ); $p->{indent} = $p->{text} x $p->{level}; } # Detect argument type and get the level value sub _get_level { my $v = shift; ref $v eq __PACKAGE__ ? $v->{level} : $v; } # ==================================================================== +===== sub new { my $class = shift; my %p = @_; my $self = { text => $p{text} // ( $p{tab} ? "\t" : " " x _lclamp( 1, $p{si +ze} // 4 ) ), EOL => $p{eol} ? "\n" : $\, level => _lclamp( 0, $p{level} // 0 ), }; _set_indent $self; bless $self, $class; } # ==================================================================== +===== use overload ( '=' => sub { my $self = shift; bless { %{ $self } }, ref $self; }, '""' => sub { shift->{indent}; }, '++' => sub { _set_indent shift, +1; }, '--' => sub { _set_indent shift, -1; }, '+' => sub { my $self = shift; my $v = shift; my %self = %{ $self }; _set_indent \%self, +_get_level($v); bless { %self }, ref $self; }, '-' => sub { my $self = shift; my $v = shift; my %self = %{ $self }; _set_indent \%self, -_get_level($v); bless { %self }, ref $self; }, ); # ==================================================================== +===== sub import { shift; goto &config if @_; } # ==================================================================== +===== my $indent; sub config { $indent = __PACKAGE__->new(@_); } sub reset { $indent = $indent - $indent; } sub over { $indent++; } sub back { $indent--; } sub print { my $self = ref $_[0] eq __PACKAGE__ ? shift : $indent; local $\ = $self->{EOL}; CORE::print $self, @_; } sub vprint { my $self = shift; local $\ = $self->{EOL}; CORE::print $self, $_ for ( @_ ); } sub printf { my $self = ref $_[0] eq __PACKAGE__ ? shift : $indent; $self->print(sprintf shift // "", @_); }; 1; # ==================================================================== +===== # EOF

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1205367]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (6)
As of 2024-04-19 11:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found