Skip to content

Better support for windows #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a1810fa
App app settings.
alexsilva Jul 11, 2019
c71ed67
Add NPM_ROOT_PATH app config.
alexsilva Jul 11, 2019
12e5bea
Fix windows issues.
alexsilva Jul 12, 2019
d2b95b8
Better command.
alexsilva Jul 12, 2019
821b5f4
Extends command args.
alexsilva Jul 12, 2019
305a1b4
Move config to apps.
alexsilva Jul 12, 2019
d39a71c
Log command and code.
alexsilva Jul 12, 2019
5939dbe
New command.
alexsilva Jul 12, 2019
69800ef
No save by default.
alexsilva Jul 12, 2019
56f5643
Letting the user choose whether saved or not dependency.
alexsilva Jul 12, 2019
16c4515
Move reference strings.
alexsilva Jul 12, 2019
3bb515a
Ignore .idea
alexsilva Jul 12, 2019
549e19e
Complete wrap.
alexsilva Jul 12, 2019
3704514
Move StdinWriter to process.
alexsilva Jul 15, 2019
2f3d109
Add compat module.
alexsilva Jul 15, 2019
87f8079
StdinWriter context.
alexsilva Jul 15, 2019
7b855aa
pep8
alexsilva Jul 15, 2019
22c5372
Update test; Add npm app.
alexsilva Jul 15, 2019
543865f
Fix app settings based tests.
alexsilva Jul 15, 2019
300aac1
Set npm_workdir at NPM_ROOT_PATH.
alexsilva Jul 15, 2019
ba8cae2
Add django-appconf has requirements.
alexsilva Jul 15, 2019
a92d9de
Configure NPM_EXECUTABLE_PATH as environ.
alexsilva Jul 15, 2019
e767c6c
Fix tests for windows.
alexsilva Jul 15, 2019
70b346b
Fix windows erro when location does not exists.
alexsilva Jul 16, 2019
8d5c55c
Fix flatten_patterns path.
alexsilva Sep 11, 2019
5991ca4
Add classifier python 3.9
alexsilva Jan 6, 2023
e78c8d1
v1.1.0
alexsilva Sep 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
build/
dist/
.cache
.idea
1 change: 1 addition & 0 deletions npm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'npm.apps.NPMConfig'
6 changes: 6 additions & 0 deletions npm/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class NPMConfig(AppConfig):
name = 'npm'
verbose_name = "NPM package installer"
4 changes: 4 additions & 0 deletions npm/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
22 changes: 22 additions & 0 deletions npm/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os
from django.conf import settings
from appconf import AppConf


class MyAppConf(AppConf):
# app settings

# Windows settings
# node_executable = "D:\\Program Files\\nodejs\\node.exe"
# npm_cli = os.path.join(os.path.dirname(node_executable),
# "node_modules\\npm\\bin\\npm-cli.js")
# NPM_EXECUTABLE_PATH = '"%s" "%s"' % (node_executable, npm_cli)
EXECUTABLE_PATH = 'npm'
ROOT_PATH = os.getcwd()

STATIC_FILES_PREFIX = ''
FINDER_USE_CACHE = True
FILE_PATTERNS = None

class Meta:
prefix = 'npm'
67 changes: 49 additions & 18 deletions npm/finders.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,66 @@
from __future__ import print_function

import os
import shlex
import subprocess
import sys
from fnmatch import fnmatch
from logging import getLogger

from django.contrib.staticfiles import utils as django_utils
from django.contrib.staticfiles.finders import FileSystemFinder
from django.core.files.storage import FileSystemStorage
from django.conf import settings

try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
from npm.compat import OrderedDict
from npm.conf import settings
from npm.process import StdinWriter

logger = getLogger(__name__)


def npm_install(**config):
"""Install nodejs packages"""
npm_executable = config.setdefault('npm_executable', settings.NPM_EXECUTABLE_PATH)
npm_workdir = config.setdefault('npm_workdir', settings.NPM_ROOT_PATH)
npm_command_args = config.setdefault('npm_command_args', ())

command = shlex.split(npm_executable)

if not npm_command_args:
command.extend(['install', '--prefix=' + settings.NPM_ROOT_PATH])
else:
command.extend(npm_command_args)

def npm_install():
npm_executable_path = getattr(settings, 'NPM_EXECUTABLE_PATH', 'npm')
command = [npm_executable_path, 'install', '--prefix=' + get_npm_root_path()]
proc = subprocess.Popen(
command,
env={'PATH': os.environ.get('PATH')},
env=os.environ,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE,
universal_newlines=True,
cwd=npm_workdir,
bufsize=2048
)
proc.wait()

with StdinWriter(proc):
try:
while proc.poll() is None:
data = proc.stdout.read(1)
if not data:
break
print(data, file=sys.stdout, end='')
finally:
proc.stdout.close()

def get_npm_root_path():
return getattr(settings, 'NPM_ROOT_PATH', '.')
logger.debug("%s %s" % (proc.poll(), command))
# npm code
return proc.poll()


def flatten_patterns(patterns):
if patterns is None:
return None
return [
os.path.join(module, module_pattern)
os.path.normpath(os.path.join(module, module_pattern))
for module, module_patterns in patterns.items()
for module_pattern in module_patterns
]
Expand Down Expand Up @@ -79,12 +108,12 @@ def get_files(storage, match_patterns='*', ignore_patterns=None, location=''):

class NpmFinder(FileSystemFinder):
def __init__(self, apps=None, *args, **kwargs):
self.node_modules_path = get_npm_root_path()
self.destination = getattr(settings, 'NPM_STATIC_FILES_PREFIX', '')
self.cache_enabled = getattr(settings, 'NPM_FINDER_USE_CACHE', True)
self.node_modules_path = settings.NPM_ROOT_PATH
self.destination = settings.NPM_STATIC_FILES_PREFIX
self.cache_enabled = settings.NPM_FINDER_USE_CACHE
self.cached_list = None

self.match_patterns = flatten_patterns(getattr(settings, 'NPM_FILE_PATTERNS', None)) or ['*']
self.match_patterns = flatten_patterns(settings.NPM_FILE_PATTERNS) or ['*']
self.locations = [(self.destination, os.path.join(self.node_modules_path, 'node_modules'))]
self.storages = OrderedDict()

Expand All @@ -108,6 +137,8 @@ def list(self, ignore_patterns=None): # TODO should be configurable, add settin

def _make_list_generator(self, ignore_patterns=None):
for prefix, root in self.locations:
if not os.path.exists(root):
continue
storage = self.storages[root]
for path in get_files(storage, self.match_patterns, ignore_patterns):
yield path, storage
15 changes: 15 additions & 0 deletions npm/management/commands/npm_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import argparse

from django.core.management.base import BaseCommand

from npm.finders import npm_install


class Command(BaseCommand):
help = 'Run npm install'

def add_arguments(self, parser):
parser.add_argument('npm_command_args', nargs=argparse.REMAINDER)

def handle(self, *args, **options):
npm_install(npm_command_args=options.get('npm_command_args', ()))
9 changes: 0 additions & 9 deletions npm/management/commands/npm_install.py

This file was deleted.

34 changes: 34 additions & 0 deletions npm/process.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import print_function

import sys
import threading


class StdinWriter(threading.Thread):
"""Reads stdin data and passes back to the process"""
def __init__(self, proc):
threading.Thread.__init__(self)
self.proc = proc
self.setDaemon(True)

def do_input(self):
data = sys.stdin.readline()
self.proc.stdin.write(data)
self.proc.stdin.flush()

def run(self):
while self.proc.poll() is None:
try:
self.do_input()
except (IOError, ValueError):
break

def close(self):
self.proc.stdin.close()

def __enter__(self):
self.start()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
8 changes: 5 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@

here = path.abspath(path.dirname(__file__))

requirements = ['django-appconf']

try:
from collections import OrderedDict
requirements = []
except ImportError:
requirements = ['ordereddict']
requirements.append('ordereddict')

setup(
name='django-npm',
version='1.0.0',
version='1.1.0',
description='A django staticfiles finder that uses npm',
url='https://github.com/kevin1024/django-npm',
author='Kevin McCarthy',
Expand All @@ -29,6 +30,7 @@
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.9'
],

keywords='django npm staticfiles',
Expand Down
26 changes: 19 additions & 7 deletions tests/test_finder.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from .util import configure_settings
import os
from os.path import normpath

from tests.util import configure_settings

configure_settings()

import pytest
Expand Down Expand Up @@ -28,34 +32,41 @@ def test_get_files(npm_dir):
files = get_files(storage, match_patterns='*')
assert any([True for _ in files])


def test_finder_list_all(npm_dir):
f = NpmFinder()
assert any([True for _ in f.list()])


def test_finder_find(npm_dir):
f = NpmFinder()
file = f.find('mocha/mocha.js')
file = f.find(normpath('mocha/mocha.js'))
assert file


def test_finder_in_subdirectory(npm_dir):
with override_settings(NPM_STATIC_FILES_PREFIX='lib'):
f = NpmFinder()
assert f.find('lib/mocha/mocha.js')
assert f.find(normpath('lib/mocha/mocha.js'))


def test_finder_with_patterns_in_subdirectory(npm_dir):
with override_settings(NPM_STATIC_FILES_PREFIX='lib', NPM_FILE_PATTERNS={'mocha': ['*']}):
f = NpmFinder()
assert f.find('lib/mocha/mocha.js')
assert f.find(normpath('lib/mocha/mocha.js'))


def test_finder_with_patterns_in_directory_component(npm_dir):
with override_settings(NPM_STATIC_FILES_PREFIX='lib', NPM_FILE_PATTERNS={'mocha': ['*/*js']}):
with override_settings(NPM_STATIC_FILES_PREFIX='lib', NPM_FILE_PATTERNS={'mocha': ['*{0.sep}*js'.format(os)]}):
f = NpmFinder()
assert f.find('lib/mocha/lib/test.js')
assert f.find(normpath('lib/mocha/lib/test.js'))


def test_no_matching_paths_returns_empty_list(npm_dir):
with override_settings(NPM_FILE_PATTERNS={'foo': ['bar']}):
f = NpmFinder()
assert f.find('mocha/mocha.js') == []
assert f.find(normpath('mocha/mocha.js')) == []


def test_finder_cache(npm_dir):
with override_settings(NPM_FINDER_USE_CACHE=True):
Expand All @@ -64,6 +75,7 @@ def test_finder_cache(npm_dir):
assert f.cached_list is not None
assert f.list() is f.cached_list


def test_finder_no_cache(npm_dir):
with override_settings(NPM_FINDER_USE_CACHE=False):
f = NpmFinder()
Expand Down
6 changes: 6 additions & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import os

from django.conf import settings
import django


def configure_settings():
settings.configure(
DEBUG=True,
INSTALLED_APPS=['npm'],
NPM_EXECUTABLE_PATH=os.environ.get('NPM_EXECUTABLE_PATH', 'npm'),
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
)
django.setup()