76eeded146ad721b93e5485da5e4247b7b40dc70
[pyimportcan.git] / pyimportcan.pl
1 #!/usr/bin/env perl
2 # pyimportcan.pl -- Python imports canonical format filter
3 # Copyright (C) 2013-2022 Sergey Matveev (stargrave@stargrave.org)
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, version 3 of the License.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 =pod
17
18 =head1 PROBLEM
19
20 Python allows various different formats of how to make importing of
21 modules and functions:
22
23     from foo import bar
24
25     from foo import bar, baz
26
27     from foo import (
28       bar,
29       baz
30     )
31
32     from foo import (
33       baz,
34       bar,
35     )
36
37     from foo import bar, \
38         baz
39
40 Everything can be unsorted at any place, thus making the task of
41 checking if import already exists rather hard. Moreover it complicates
42 just adding another import, because you have to find it first and deal
43 with the format it is already written.
44
45 Everything can use different indentation practices. Everything can be
46 doubled and you won't notice it:
47
48     from foo import bar
49     [many lines]
50     from foo import (
51         baz,
52         bar
53     )
54
55 Allowing multiple imports on one line leads to possibility of long lines
56 appearing.
57
58 The whole import sections with various formats just can not be
59 technically sorted with the editor (no way to do "vip:sort<CR>" in Vim).
60
61 Merge conflict in the imports section can be not so trivial to resolve,
62 however in most cases just aggregating them will be enough.
63
64 If you want to find the code that imports bar from foo, then you can do
65 it with "from foo import bar", but can not with "import (\n" is most
66 cases. You are forced to use special Python-related software to deal
67 with it, but no grep or sed.
68
69 =head1 SOLUTION
70
71 Use the very convenient simple single format. Single import per line,
72 period. Sorted and only identical ones. With git merge conflict lines
73 removed. This script reads stdin with import-lines and writes canonical
74 representation of them to stdout. For example you can select lines in
75 Vim and just enter ":!pyimportcan.pl".
76
77 From that kind of imports:
78
79     from waaccounts.models import OrgRole
80     import math
81     from waaccounts.models import UserAccount
82     from waadv.filters import AdvCampaignFilterSet as foo1
83     from waadv.filters import AdvCampaignBidFilterSet
84     from waadv.filters import AdvCampaignUniqueShowFilterSet as foo2
85     from waadv.filters import VasttaskFilterSet
86     from waadv.forms import AdvCampaignCreateForm
87     from waadv.forms import PriceListTotalBudgetDiscountFormset
88     from waadv.models import Adv
89     from waadv.models import PriceListTotalBudgetDiscount
90     from waadv.models import VastTask
91     from wacontent.models import VrContractGroup
92     from wacatalog.models import AdvGroup
93     from wahelpers.counters import adv_campaign_click_counter_loader
94     from wahelpers.counters import adv_video_view_fact_counter_loader
95     from wahelpers.counters import adv_view_percent
96     from wahelpers.counters import ctr
97     from wahelpers.functions import get_json_rpc_proxy, get_return_url
98     <<<<<<< HEAD
99     from waorigin.kendo_menus.toolbar import KendoGridToolbar
100     from waorigin.kendo_menus.toolbar_button import KendoToolbarButton
101     =======
102     from http_utils import JSONResponse
103     from wahelpers.counters import (
104         adv_campaign_click_counter_loader,
105         adv_campaign_click_fact_counter_loader,
106         ctr,
107         adv_view_percent,
108     )
109     from waorigin.kendo_menus.toolbar_button import (
110     AddKendoToolbarButton,
111     StatusKendoToolbarButton
112     )
113     from waorigin.models import Content, ContentCategory3 as foo4, ContentCategory2, Files
114     from waorigin.views import filter_view, model_autocompletion, \
115         create_update_with_auto_user, get_objects_for_edit_many
116     from wapartners.models import PrNomenclature, PrSite
117     >>>>>>> 1bf0b64... fixed view refs #11992
118     from waorigin.kendo_data_source import KendoDataSourceView
119     from waorigin.models import Content
120     from waorigin.models import ContentCategory2
121     from waorigin.models import ContentCategory3
122     from waorigin.models import Files
123     from waorigin.views import create_update_with_auto_user
124     from waorigin.views import filter_view
125     from waorigin.views import get_objects_for_edit_many
126     from waorigin.views import model_autocompletion
127     from wapartners.models import PrSite
128     from wapartners.models import PrNomenclature
129
130 it makes the following one (by feeding it to stdin and capturing on
131 stdout):
132
133     import math
134     from http_utils import JSONResponse
135     from waaccounts.models import OrgRole
136     from waaccounts.models import UserAccount
137     from waadv.filters import AdvCampaignBidFilterSet
138     from waadv.filters import AdvCampaignFilterSet as foo1
139     from waadv.filters import AdvCampaignUniqueShowFilterSet as foo2
140     from waadv.filters import VasttaskFilterSet
141     from waadv.forms import AdvCampaignCreateForm
142     from waadv.forms import PriceListTotalBudgetDiscountFormset
143     from waadv.models import Adv
144     from waadv.models import PriceListTotalBudgetDiscount
145     from waadv.models import VastTask
146     from wacatalog.models import AdvGroup
147     from wacontent.models import VrContractGroup
148     from wahelpers.counters import adv_campaign_click_counter_loader
149     from wahelpers.counters import adv_campaign_click_fact_counter_loader
150     from wahelpers.counters import adv_video_view_fact_counter_loader
151     from wahelpers.counters import adv_view_percent
152     from wahelpers.counters import ctr
153     from wahelpers.functions import get_json_rpc_proxy
154     from wahelpers.functions import get_return_url
155     from waorigin.kendo_data_source import KendoDataSourceView
156     from waorigin.kendo_menus.toolbar import KendoGridToolbar
157     from waorigin.kendo_menus.toolbar_button import AddKendoToolbarButton
158     from waorigin.kendo_menus.toolbar_button import KendoToolbarButton
159     from waorigin.kendo_menus.toolbar_button import StatusKendoToolbarButton
160     from waorigin.models import Content
161     from waorigin.models import ContentCategory2
162     from waorigin.models import ContentCategory3
163     from waorigin.models import ContentCategory3 as foo4
164     from waorigin.models import Files
165     from waorigin.views import create_update_with_auto_user
166     from waorigin.views import filter_view
167     from waorigin.views import get_objects_for_edit_many
168     from waorigin.views import model_autocompletion
169     from wapartners.models import PrNomenclature
170     from wapartners.models import PrSite
171
172 =head1 AUTHOR
173
174 Sergey Matveev L<mailto:stargrave@stargrave.org>
175
176 =cut
177
178 use strict;
179 use warnings;
180
181 my $buf;
182 my $con;
183 my @imports;
184 my %parsed;
185
186 # Collect strings and aggregate the splitted ones
187 while(<>){
188     next if /^[<=>]{2,}/;
189     next if /^\s*#/;
190     chop;
191     $buf = ($con ? $buf : "") . $_;
192     $con = /[\\,\(]\s*$/ ? 1 : 0;
193     next if /^\s*\)*\s*$/;
194     push @imports, $buf;
195 };
196
197 # Consolidate information from where what is imported
198 foreach (@imports) {
199     s/[\\\(\)]//g;
200     s/  */ /g;
201     next if /import\s*$/;
202     /^(.*)\s*import\s*(.*)$/;
203     my ($where, $what) = ($1, $2);
204     map { $parsed{$where}->{$_}++ } split /\s*,\s*/, $what;
205 };
206
207 foreach my $where (sort keys %parsed){
208     map { print $where . "import $_\n" } sort keys %{$parsed{$where}};
209 };