[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#762944: debsources: make .pc/ exclusion a configuration parameter



Hello Stefano,

I have attached two patches. One fixes the issues we talked about
earlier today, and the other implements the "Show / Hide hidden files"
button in directory listing:

On 10/03/15 09:43 AM, Stefano Zacchiroli wrote:
> On Mon, Mar 09, 2015 at 02:59:54PM -0400, Jason Pleau wrote:
>> Updated patch that fixes an error if hidden_files is empty
> 
> Hey Jason, thanks a lot for your patch! It looks great in general. I
> just have a few minor changes to request, if you don't mind. See below
> for details.
> 
>> Right now the .pc/ exclusion in directory listings is hardcoded. This
>> new hidden_files configuration allows us to be more flexible in what is
>> shown or hidden in those listings.
>>
>> */.pc/ is added as a default value for this configuration setting.
> 
> This is one concern I have. You've modified the sample config file, but
> not the default configuration stored in mainlib.py. I haven't tested
> that, but I suspect that for that reason upgrading a currently deployed
> Debsources instance to a version that include your patch will trigger
> failures at runtime, if config.local.ini isn't updated. Can you confirm
> that?
> 
> If so, I'd very much prefer changing DEFAULT_CONFIG in mainlib.py to
> include */.pc/ as default setting to 1) avoid breakages, and 2) retain
> the current behavior.

As talked on IRC, I have added */.pc/ to DEFAULT_CONFIG.web_app.

> 
>> +# space-separated list of files or directories patterns to hide in 
>> +# directory listings
>> +hidden_files: */.pc/
> 
> It would be nice to document in the comment here (for lack of a better
> place...) the intended semantics of hidden_files, i.e.: match on the
> full path, relative to which dir, and maybe the fact that fnmatch() is
> involved, for reference.
> 
I have added a bit more comments, feel free to adjust if you think you
can phrase it better than it is currently (I have no shame in admitting
that I may sometimes not be clear enough in English :) )

> There are also a couple of minor flake8 issues in your patch (which I
> can fix upon patch import, if needed).

They now seem fixed on my side, you can re-run flake8 to confirm !

> 
> 
> And from your other email:
>> This also allow us to add a toggle button (show / hide hidden files) in
>> directory listings eventually. I could do that in a separate commit in
>> this bug if you'd like.
> 
> Yes please, that would be awesome! :)
> 

Done :) See 0002 patch !

> 
> Cheers.
> 

Thanks

-- 
Jason Pleau
>From 641a210eb0d06dfc63a9332891981bcfd07e3426 Mon Sep 17 00:00:00 2001
From: Jason Pleau <jason@jpleau.ca>
Date: Tue, 10 Mar 2015 20:37:22 -0400
Subject: [PATCH 2/2] source_folder: add a show/hide hidden files button

Since that hidden files and directories are still passed to the view,
but are now given an 'hidden' boolean attribute, we can show or hide
them once the page is loaded.

I also changed the way javascript was loaded in debsources.js, so that
it does not run unnecessary code (for example we don't need to print
line numbers in folders).
---
 debsources/app/static/css/source_folder.css    |  13 ++
 debsources/app/static/javascript/debsources.js | 273 ++++++++++++++-----------
 debsources/app/templates/source_file.html      |   2 +-
 debsources/app/templates/source_folder.html    |  59 +++---
 debsources/app/views.py                        |   1 +
 5 files changed, 196 insertions(+), 152 deletions(-)

diff --git a/debsources/app/static/css/source_folder.css b/debsources/app/static/css/source_folder.css
index 1a45b8f..1bd9c6e 100644
--- a/debsources/app/static/css/source_folder.css
+++ b/debsources/app/static/css/source_folder.css
@@ -40,3 +40,16 @@ License: GNU Affero General Public License, version 3 or above.
     font-size: small;
     text-align: right;
 }
+
+.hidden_file {
+    display: none;
+}
+
+.hidden_file.visible {
+    display: table-row;
+}
+
+#btn_toggle_hidden_files {
+	font-size: 0.8em;
+}
+
diff --git a/debsources/app/static/javascript/debsources.js b/debsources/app/static/javascript/debsources.js
index 844e58a..ced5cf6 100644
--- a/debsources/app/static/javascript/debsources.js
+++ b/debsources/app/static/javascript/debsources.js
@@ -1,4 +1,4 @@
-/* Copyright (C) 2014  Jason Pleau <jason@jpleau.ca>
+/* Copyright (C) 2015  Jason Pleau <jason@jpleau.ca>
  *
  * This file is part of Debsources.
  *
@@ -27,128 +27,151 @@
  *
  */
 
-var debsources = function(message_pos) {
-    var print_lines = function() {
-        var position = message_pos;
-        var msgbox = document.getElementById('messages');
-        var index = document.getElementById('sourceslinenumbers');
-        var divHeight = msgbox.offsetHeight;
-        var lineHeight = parseInt(window.getComputedStyle(index).getPropertyValue('line-height'),10);
-        var lines = Math.ceil(divHeight / lineHeight)+1; // always insert one more line below the last line of code
-
-        for(i=0; i<lines; ++i){
-            var element = document.createElement('a');
-            var s = '<a></a><br>'; // lines corr. messages do no need indexes
-            element.innerHTML = s;
-            var refnode = document.getElementById('L'+position.toString());
-            refnode.parentNode.insertBefore(element,refnode.nextSibling);} //insert after the node with assigned position
-    };
-
-    function highlight_lines(start, end) {
-        // First, remove the highlight class from elements that might already have it
-        var elements = document.querySelectorAll("span.highlight");
-        for (i = 0; i < elements.length; ++i) {
-            var element = elements[i];
-            element.className = element.className.replace(/\bhighlight\b/, '');
-        }
-
-        // Then, add the highlight class to elements that contain the lines we want to highlight
-        for (i = start; i <= end; ++i) {
-            var element = document.getElementById("line" + i);
-            element.className = element.className + " highlight ";
-        }
-    }
-
-    var hash_changed = function(event, scroll) {
-
-        event = typeof event !== 'undefined' ? event: null;
-        scroll = typeof scroll !== 'undefined' ? scroll: false;
-
-        // Will match strings like #L15 and #L15-L20
-        var regex = /#L(\d+)(-L(\d+))*$/;
-
-        var match = regex.exec(window.location.hash);
-        if (match != null) {
-            var first_line = second_line = null;
-            first_line = parseInt(match[1]);
-
-            if (typeof match[3] !== 'undefined' && match[3].length > 0) {
-                second_line = parseInt(match[3]);
-            } else {
-                second_line = first_line;
-            }
-
-            // If we get something like #L20-L15, just swap the two line numbers so the loop will work
-            if (second_line < first_line) {
-                var tmp = first_line;
-                first_line = second_line;
-                second_line = tmp;
-            }
-
-            highlight_lines(first_line, second_line);
-
-            if (scroll) {
-                window.scroll(0, document.getElementById("L"+first_line).offsetTop);
-            }
-        }
-    };
-
-
-    function change_hash_without_scroll(element, hash) {
-        // This is necessary because when changing window.location.hash, the window will
-        // scroll to the element's id if it matches the hash
-        var id = element.id;
-        element.id = id+'-tmpNoScroll';
-        window.location.hash = hash;
-        element.id = id;
-    }
-
-    var last_clicked;
-    var line_click_handler = function(event) {
-        if (event.preventDefault) {
-            event.preventDefault();
-        } else {
-            event.returnValue = false;
-        }
-
-        var callerElement = event.target || event.srcElement;
-
-        if (!event.shiftKey || !last_clicked) {
-            last_clicked = callerElement;
-            change_hash_without_scroll(callerElement, "L" + (callerElement.textContent || callerElement.innerText));
-        } else {
-            var first_line = parseInt(last_clicked.textContent || last_clicked.innerText);
-            var second_line = parseInt(callerElement.textContent || callerElement.innerText);
-
-            if (second_line < first_line) {
-                var tmp = first_line;
-                first_line = second_line;
-                second_line = tmp;
-            }
-
-            change_hash_without_scroll(callerElement, "L" + first_line + "-L" + second_line);
-        }
-    };
-
-    var window_load_sourcecode = function(event) {
-        var line_numbers = document.querySelectorAll("#sourceslinenumbers a");
-        for (i = 0; i < line_numbers.length; ++i) {
-            var line_number_element = line_numbers[i];
-            if (line_number_element.addEventListener) {
-                line_number_element.addEventListener('click', line_click_handler, false);
-            } else {
-                line_number_element.attachEvent('onclick',  line_click_handler);
-            }
-        }
-        hash_changed(null, true);
-    };
-
-    if (window.addEventListener) {
-        window.addEventListener('load', window_load_sourcecode, false);
-    } else {
-        window.attachEvent('onload', window_load_sourcecode);
-    }
-
-    window.onhashchange = hash_changed;
-    window.onload = print_lines;
+var debsources = {
+	source_file: function(message_pos) {
+		var print_lines = function() {
+			var position = message_pos;
+			var msgbox = document.getElementById('messages');
+			var index = document.getElementById('sourceslinenumbers');
+			var divHeight = msgbox.offsetHeight;
+			var lineHeight = parseInt(window.getComputedStyle(index).getPropertyValue('line-height'),10);
+			var lines = Math.ceil(divHeight / lineHeight)+1; // always insert one more line below the last line of code
+
+			for(i=0; i<lines; ++i){
+				var element = document.createElement('a');
+				var s = '<a></a><br>'; // lines corr. messages do no need indexes
+				element.innerHTML = s;
+				var refnode = document.getElementById('L'+position.toString());
+				refnode.parentNode.insertBefore(element,refnode.nextSibling);} //insert after the node with assigned position
+		};
+
+		function highlight_lines(start, end) {
+			// First, remove the highlight class from elements that might already have it
+			var elements = document.querySelectorAll("span.highlight");
+			for (i = 0; i < elements.length; ++i) {
+				var element = elements[i];
+				element.className = element.className.replace(/\bhighlight\b/, '');
+			}
+
+			// Then, add the highlight class to elements that contain the lines we want to highlight
+			for (i = start; i <= end; ++i) {
+				var element = document.getElementById("line" + i);
+				element.className = element.className + " highlight ";
+			}
+		}
+
+		var hash_changed = function(event, scroll) {
+
+			event = typeof event !== 'undefined' ? event: null;
+			scroll = typeof scroll !== 'undefined' ? scroll: false;
+
+			// Will match strings like #L15 and #L15-L20
+			var regex = /#L(\d+)(-L(\d+))*$/;
+
+			var match = regex.exec(window.location.hash);
+			if (match != null) {
+				var first_line = second_line = null;
+				first_line = parseInt(match[1]);
+
+				if (typeof match[3] !== 'undefined' && match[3].length > 0) {
+					second_line = parseInt(match[3]);
+				} else {
+					second_line = first_line;
+				}
+
+				// If we get something like #L20-L15, just swap the two line numbers so the loop will work
+				if (second_line < first_line) {
+					var tmp = first_line;
+					first_line = second_line;
+					second_line = tmp;
+				}
+
+				highlight_lines(first_line, second_line);
+
+				if (scroll) {
+					window.scroll(0, document.getElementById("L"+first_line).offsetTop);
+				}
+			}
+		};
+
+
+		function change_hash_without_scroll(element, hash) {
+			// This is necessary because when changing window.location.hash, the window will
+			// scroll to the element's id if it matches the hash
+			var id = element.id;
+			element.id = id+'-tmpNoScroll';
+			window.location.hash = hash;
+			element.id = id;
+		}
+
+		var last_clicked;
+		var line_click_handler = function(event) {
+			if (event.preventDefault) {
+				event.preventDefault();
+			} else {
+				event.returnValue = false;
+			}
+
+			var callerElement = event.target || event.srcElement;
+
+			if (!event.shiftKey || !last_clicked) {
+				last_clicked = callerElement;
+				change_hash_without_scroll(callerElement, "L" + (callerElement.textContent || callerElement.innerText));
+			} else {
+				var first_line = parseInt(last_clicked.textContent || last_clicked.innerText);
+				var second_line = parseInt(callerElement.textContent || callerElement.innerText);
+
+				if (second_line < first_line) {
+					var tmp = first_line;
+					first_line = second_line;
+					second_line = tmp;
+				}
+
+				change_hash_without_scroll(callerElement, "L" + first_line + "-L" + second_line);
+			}
+		};
+
+		var window_load_sourcecode = function(event) {
+			var line_numbers = document.querySelectorAll("#sourceslinenumbers a");
+			for (i = 0; i < line_numbers.length; ++i) {
+				var line_number_element = line_numbers[i];
+				if (line_number_element.addEventListener) {
+					line_number_element.addEventListener('click', line_click_handler, false);
+				} else {
+					line_number_element.attachEvent('onclick',  line_click_handler);
+				}
+			}
+			hash_changed(null, true);
+		};
+
+		if (window.addEventListener) {
+			window.addEventListener('load', window_load_sourcecode, false);
+		} else {
+			window.attachEvent('onload', window_load_sourcecode);
+		}
+
+		window.onhashchange = hash_changed;
+		window.onload = print_lines;
+	},
+
+	source_folder: function() {
+		document.getElementById("btn_toggle_hidden_files").onclick = function(event) {
+			event.preventDefault();
+			var action = this.getAttribute('data-action');
+			var actionTextElement = document.querySelectorAll("#btn_toggle_hidden_files span")[0];
+			var elements = document.querySelectorAll(".dir-listing tr.hidden_file");
+			for (i = 0; i < elements.length; ++i) {
+				var element = elements[i];
+				if (action == "show") {
+					element.className = element.className + " visible";
+					action = "Hide";
+				} else {
+					element.className = element.className.replace(/\bvisible\b/, '');
+					action = "Show";
+				}
+				actionTextElement.innerText = action;
+				this.setAttribute('data-action', action.toLowerCase());
+			}
+		}
+	}
 };
diff --git a/debsources/app/templates/source_file.html b/debsources/app/templates/source_file.html
index 675d1dc..641750b 100644
--- a/debsources/app/templates/source_file.html
+++ b/debsources/app/templates/source_file.html
@@ -31,7 +31,7 @@
 {% include "source_file_code.inc.html" %}
 
 <script type="text/javascript">
-  debsources("{{ msg.position }}");
+  debsources.source_file("{{ msg.position }}");
   hljs.highlightBlock(document.getElementById('sourcecode'))
 </script>
 
diff --git a/debsources/app/templates/source_folder.html b/debsources/app/templates/source_folder.html
index 99ca1c1..3ac7980 100644
--- a/debsources/app/templates/source_folder.html
+++ b/debsources/app/templates/source_folder.html
@@ -9,6 +9,8 @@
 {{ super() }}
 <link rel="stylesheet" type="text/css"
       href="{{ url_for('static', filename='css/source_folder.css') }}" />
+<script src="{{ url_for('static', filename='javascript/debsources.js') }}"></script>
+
 {% endblock %}
 
 {% block title %}Folder: {{ directory }}{% endblock %}
@@ -20,8 +22,13 @@
 {{ infobox.render_infobox(pathl[0][0], pathl[1][0], pkg_infos) }}
 {% endif %}
 
+
 <h2 id="folder_title">{{ self.title() }}</h2>
 
+{% if nb_hidden_files %}
+  <a href="#" id="btn_toggle_hidden_files" data-action="show"><span>Show</span> hidden files ({{ nb_hidden_files }})</a>
+{% endif %}
+
 <table class="dir-listing">
   <tr>
     <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/places/folder.png" alt="d " /></td>
@@ -32,36 +39,36 @@
   </tr>
 
 {% for dir in subdirs %}
-  {% if not dir['hidden'] %}
-    <tr>
-      <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/places/folder.png" alt="d " /></td>
-      {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ dir.stat.type }}</span></td>{% endif %}
-      <td class="stat-perms"><span>{{ dir.stat.perms }}</span></td>
-      {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(dir.stat.size) }}</span></td>{% endif %}
-      <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+dir.name) }}">{{ dir.name }}</a>
-        {% if config["DIR_LS_LONG"] %}
-          {% if dir.stat.symlink_dest is not none %}{{ " â?? " + dir.stat.symlink_dest }}{% endif %}
-        {% endif %}
-      </td>
-    </tr>
-  {% endif %}
+  <tr{% if dir['hidden'] %} class="hidden_file"{% endif %}>
+    <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/places/folder.png" alt="d " /></td>
+    {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ dir.stat.type }}</span></td>{% endif %}
+    <td class="stat-perms"><span>{{ dir.stat.perms }}</span></td>
+    {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(dir.stat.size) }}</span></td>{% endif %}
+    <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+dir.name) }}">{{ dir.name }}</a>
+      {% if config["DIR_LS_LONG"] %}
+        {% if dir.stat.symlink_dest is not none %}{{ " â?? " + dir.stat.symlink_dest }}{% endif %}
+      {% endif %}
+    </td>
+  </tr>
 {% endfor %}
 
 {% for file_ in subfiles %}
-  {% if not file_['hidden'] %}
-    <tr>
-      <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/mimetypes/ascii.png" alt="- " /></td>
-      {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ file_.stat.type }}</span></td>{% endif %}
-      <td class="stat-perms"><span>{{ file_.stat.perms }}</span></td>
-      {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(file_.stat.size) }}</span></td>{% endif %}
-      <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+file_.name) }}">{{ file_.name }}</a>
-        {% if config["DIR_LS_LONG"] %}
-          {% if file_.stat.symlink_dest is not none %}{{ " â?? " + file_.stat.symlink_dest }}{% endif %}
-        {% endif %}
-      </td>
-    </tr>
-  {% endif %}
+  <tr{% if file_['hidden'] %} class="hidden_file"{% endif %}>
+    <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/mimetypes/ascii.png" alt="- " /></td>
+    {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ file_.stat.type }}</span></td>{% endif %}
+    <td class="stat-perms"><span>{{ file_.stat.perms }}</span></td>
+    {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(file_.stat.size) }}</span></td>{% endif %}
+    <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+file_.name) }}">{{ file_.name }}</a>
+      {% if config["DIR_LS_LONG"] %}
+        {% if file_.stat.symlink_dest is not none %}{{ " â?? " + file_.stat.symlink_dest }}{% endif %}
+      {% endif %}
+    </td>
+  </tr>
 {% endfor %}
 </table>
 
+<script type="text/javascript">
+  debsources.source_folder();
+</script>
+
 {% endblock %}
diff --git a/debsources/app/views.py b/debsources/app/views.py
index 6a99377..81c7151 100644
--- a/debsources/app/views.py
+++ b/debsources/app/views.py
@@ -668,6 +668,7 @@ def render_source_file_html(templatename, **kwargs):
                            kwargs['content']),
             subfiles=filter(lambda x: x['type'] == "file", kwargs['content']),
             pathl=Location.get_path_links("source_html", kwargs['path']),
+            nb_hidden_files=sum(1 for f in kwargs['content'] if f['hidden']),
             **kwargs)
     else:  # file
         # more work to do with files
-- 
2.1.4

>From 2ef41333760d7ca48a8a3300ea4e518eacc2fdea Mon Sep 17 00:00:00 2001
From: Jason Pleau <jason@jpleau.ca>
Date: Mon, 9 Mar 2015 12:34:06 -0400
Subject: [PATCH 1/2] add a new hidden_files setting in config.ini

Right now the .pc/ exclusion in directory listings is hardcoded. This
new hidden_files configuration allows us to be more flexible in what is
shown or hidden in those listings.

*/.pc/ is added as a default value for this configuration setting.

Closes #762944
---
 debsources/app/templates/source_folder.html | 48 ++++++++++++++++-------------
 debsources/app/views.py                     |  5 ++-
 debsources/mainlib.py                       |  4 ++-
 debsources/models.py                        | 18 +++++++----
 debsources/tests/test_webapp.py             | 21 +++++++++++++
 doc/examples/sample-config.local.ini        | 12 ++++++++
 etc/config.ini                              | 12 ++++++++
 7 files changed, 88 insertions(+), 32 deletions(-)

diff --git a/debsources/app/templates/source_folder.html b/debsources/app/templates/source_folder.html
index 444194a..99ca1c1 100644
--- a/debsources/app/templates/source_folder.html
+++ b/debsources/app/templates/source_folder.html
@@ -32,31 +32,35 @@
   </tr>
 
 {% for dir in subdirs %}
-  <tr>
-    <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/places/folder.png" alt="d " /></td>
-    {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ dir.stat.type }}</span></td>{% endif %}
-    <td class="stat-perms"><span>{{ dir.stat.perms }}</span></td>
-    {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(dir.stat.size) }}</span></td>{% endif %}
-    <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+dir.name) }}">{{ dir.name }}</a>
-      {% if config["DIR_LS_LONG"] %}
-        {% if dir.stat.symlink_dest is not none %}{{ " â?? " + dir.stat.symlink_dest }}{% endif %}
-      {% endif %}
-    </td>
-  </tr>
+  {% if not dir['hidden'] %}
+    <tr>
+      <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/places/folder.png" alt="d " /></td>
+      {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ dir.stat.type }}</span></td>{% endif %}
+      <td class="stat-perms"><span>{{ dir.stat.perms }}</span></td>
+      {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(dir.stat.size) }}</span></td>{% endif %}
+      <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+dir.name) }}">{{ dir.name }}</a>
+        {% if config["DIR_LS_LONG"] %}
+          {% if dir.stat.symlink_dest is not none %}{{ " â?? " + dir.stat.symlink_dest }}{% endif %}
+        {% endif %}
+      </td>
+    </tr>
+  {% endif %}
 {% endfor %}
 
 {% for file_ in subfiles %}
-  <tr>
-    <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/mimetypes/ascii.png" alt="- " /></td>
-    {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ file_.stat.type }}</span></td>{% endif %}
-    <td class="stat-perms"><span>{{ file_.stat.perms }}</span></td>
-    {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(file_.stat.size) }}</span></td>{% endif %}
-    <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+file_.name) }}">{{ file_.name }}</a>
-      {% if config["DIR_LS_LONG"] %}
-        {% if file_.stat.symlink_dest is not none %}{{ " â?? " + file_.stat.symlink_dest }}{% endif %}
-      {% endif %}
-    </td>
-  </tr>
+  {% if not file_['hidden'] %}
+    <tr>
+      <td class="item-img"><img src="{{ config['ICONS_FOLDER'] }}22x22/mimetypes/ascii.png" alt="- " /></td>
+      {% if config["DIR_LS_LONG"] %}<td class="stat-type"><span>{{ file_.stat.type }}</span></td>{% endif %}
+      <td class="stat-perms"><span>{{ file_.stat.perms }}</span></td>
+      {% if config["DIR_LS_LONG"] %}<td class="stat-size"><span>{{ "{:,d}".format(file_.stat.size) }}</span></td>{% endif %}
+      <td class="item-name"><a href="{{ url_for('source_html', path_to=path+'/'+file_.name) }}">{{ file_.name }}</a>
+        {% if config["DIR_LS_LONG"] %}
+          {% if file_.stat.symlink_dest is not none %}{{ " â?? " + file_.stat.symlink_dest }}{% endif %}
+        {% endif %}
+      </td>
+    </tr>
+  {% endif %}
 {% endfor %}
 </table>
 
diff --git a/debsources/app/views.py b/debsources/app/views.py
index 2519c5f..6a99377 100644
--- a/debsources/app/views.py
+++ b/debsources/app/views.py
@@ -554,10 +554,9 @@ class SourceView(GeneralView):
         """
         renders a directory, lists subdirs and subfiles
         """
-        directory = Directory(location, toplevel=(location.get_path() == ""))
 
-        # (if path == "", then the dir is toplevel, and we don't want
-        # the .pc directory)
+        hidden_files = app.config['HIDDEN_FILES'].split(" ")
+        directory = Directory(location, hidden_files)
 
         pkg_infos = Infobox(session,
                             location.get_package(),
diff --git a/debsources/mainlib.py b/debsources/mainlib.py
index 45a206b..cc0acf6 100644
--- a/debsources/mainlib.py
+++ b/debsources/mainlib.py
@@ -41,7 +41,9 @@ DEFAULT_CONFIG.update({
         'force_triggers': [],
         'single_transaction': 'true',
         },
-    'webapp': {},
+    'webapp': {
+        'hidden_files': '*/*.pc/'
+    },
 })
 
 LOG_FMT_FILE = '%(asctime)s %(module)s:%(levelname)s %(message)s'
diff --git a/debsources/models.py b/debsources/models.py
index e4d05fc..8f72dc3 100644
--- a/debsources/models.py
+++ b/debsources/models.py
@@ -19,6 +19,7 @@
 import os
 import magic
 import stat
+import fnmatch
 from collections import namedtuple
 
 from sqlalchemy import Column, ForeignKey
@@ -652,11 +653,10 @@ class Location(object):
 class Directory(object):
     """ a folder in a package """
 
-    def __init__(self, location, toplevel=False):
-        # if the directory is a toplevel one, we remove the .pc folder
+    def __init__(self, location, hidden_files=[]):
         self.sources_path = location.sources_path
-        self.toplevel = toplevel
         self.location = location
+        self.hidden_files = hidden_files
 
     def get_listing(self):
         """
@@ -670,11 +670,17 @@ class Directory(object):
             else:
                 return "file"
         get_stat, join_path = self.location.get_stat, os.path.join
-        listing = sorted(dict(name=f, type=get_type(f),
+        listing = sorted(dict(name=f, type=get_type(f), hidden=False,
                               stat=get_stat(join_path(self.sources_path, f)))
                          for f in os.listdir(self.sources_path))
-        if self.toplevel:
-            listing = filter(lambda x: x['name'] != ".pc", listing)
+
+        for hidden_file in self.hidden_files:
+            for f in listing:
+                full_path = os.path.join(self.location.sources_path, f['name'])
+                if f['type'] == "directory":
+                    full_path += "/"
+                f['hidden'] = (f['hidden']
+                               or fnmatch.fnmatch(full_path, hidden_file))
 
         return listing
 
diff --git a/debsources/tests/test_webapp.py b/debsources/tests/test_webapp.py
index 57e6d1f..52979ea 100644
--- a/debsources/tests/test_webapp.py
+++ b/debsources/tests/test_webapp.py
@@ -272,12 +272,33 @@ class DebsourcesTestCase(unittest.TestCase, DbTestFixture):
         self.assertEqual(rv['directory'], "2.01-6")
         self.assertIn({"type": "file",
                        "name": "ledit.ml",
+                       "hidden": False,
                        "stat": {"perms": "rw-r--r--",
                                 "size": 45858,
                                 "type": "-",
                                 "symlink_dest": None}
                        }, rv['content'])
 
+    def test_api_hidden_files_folder(self):
+        rv = json.loads(self.app.get('/api/src/nvidia-xconfig/319.72-1/').data)
+        self.assertIn({"type": "directory",
+                       "name": ".pc",
+                       "hidden": True,
+                       "stat": {"perms": "rwxr-xr-x",
+                                "size": 4096,
+                                "type": "d",
+                                "symlink_dest": None}
+                       }, rv['content'])
+
+        self.assertIn({"type": "file",
+                       "name": "lscf.c",
+                       "hidden": False,
+                       "stat": {"perms": "rw-r--r--",
+                                "size": 11940,
+                                "type": "-",
+                                "symlink_dest": None}
+                       }, rv['content'])
+
     def test_api_symlink_dest(self):
         rv = json.loads(self.app.get('/api/src/beignet/1.0.0-1/').data)
         self.assertIn({"type": "file",
diff --git a/doc/examples/sample-config.local.ini b/doc/examples/sample-config.local.ini
index 96973d6..06ed0a1 100644
--- a/doc/examples/sample-config.local.ini
+++ b/doc/examples/sample-config.local.ini
@@ -78,3 +78,15 @@ serve_static_files: true
 
 # where to log webapp specific information
 log_file: %(log_dir)s/webapp.log
+
+# Space-separated list of files or directories patterns to hide in
+# directory listings.
+#
+# The *full path* of files / directories from a source package's directory will
+# be compared using fnmatch.fnmatch.
+#
+# Example, we have a */.pc/ pattern, it means that
+# /srv/[...]/package_name/version/.pc/ will be hidden, as well as
+# /src/[...]/package_name/version/debian/submodule/.pc/
+
+hidden_files: */.pc/
diff --git a/etc/config.ini b/etc/config.ini
index 1f27d17..ffb4eff 100644
--- a/etc/config.ini
+++ b/etc/config.ini
@@ -85,3 +85,15 @@ log_file: %(log_dir)s/webapp.log
 
 # whether to enable the blueprint
 blueprint_copyright: true
+
+# Space-separated list of files or directories patterns to hide in
+# directory listings.
+#
+# The *full path* of files / directories from a source package's directory will
+# be compared using fnmatch.fnmatch.
+#
+# Example, we have a */.pc/ pattern, it means that
+# /srv/[...]/package_name/version/.pc/ will be hidden, as well as
+# /src/[...]/package_name/version/debian/submodule/.pc/
+
+hidden_files: */.pc/
-- 
2.1.4


Reply to: