Giter Club home page Giter Club logo

nim-schedules's Introduction

nim-schedules

A Nim scheduler library that lets you kick off jobs at regular intervals.

Read the documentation.

Features:

  • Simple to use API for scheduling jobs.
  • Support scheduling both async and sync procs.
  • Lightweight and zero dependencies.

Getting Started

$ nimble install schedules

Usage

# File: scheduleExample.nim
import schedules, times, asyncdispatch

schedules:
  every(seconds=10, id="tick"):
    echo("tick", now())

  every(seconds=10, id="atick", async=true):
    echo("tick", now())
    await sleepAsync(3000)
  1. Schedule thread proc every 10 seconds.
  2. Schedule async proc every 10 seconds.

Run:

nim c --threads:on -r scheduleExample.nim

Note:

  • Don't forget --threads:on when compiling your application.
  • The library schedules all jobs at a regular interval, but it'll be impacted by your system load.

Advance Usages

Cron

You can use cron to schedule jobs using cron-like syntax.

import schedules, times, asyncdispatch

schedules:
  cron(minute="*/1", hour="*", day_of_month="*", month="*", day_of_week="*", id="tick"):
    echo("tick", now())

  cron(minute="*/1", hour="*", day_of_month="*", month="*", day_of_week="*", id="atick", async=true):
    echo("tick", now())
    await sleepAsync(3000)
  1. Schedule thread proc every minute.
  2. Schedule async proc every minute.

Throttling

By default, only one instance of the job is to be scheduled at the same time. If a job hasn't finished but the next run time has come, the next job will not be scheduled.

You can allow more instances by specifying throttle=. For example:

import schedules, times, asyncdispatch, os

schedules:
  every(seconds=1, id="tick", throttle=2):
    echo("tick", now())
    sleep(2000)

  every(seconds=1, id="async tick", async=true, throttle=2):
    echo("async tick", now())
    await sleepAsync(4000)

Customize Scheduler

Sometimes, you want to run the scheduler in parallel with other libraries. In this case, you can create your own scheduler by macro scheduler and start it later.

Below is an example of co-exist jester and nim-schedules in one process.

import times, asyncdispatch, schedules, jester

scheduler mySched:
  every(seconds=1, id="sync tick"):
    echo("sync tick, seconds=1 ", now())

router myRouter:
  get "/":
    resp "It's alive!"

proc main():
  # start schedules
  asyncCheck mySched.start()

  # start jester
  let port = paramStr(1).parseInt().Port
  let settings = newSettings(port=port)
  var jester = initJester(myrouter, settings=settings)

  # run
  jester.serve()

when isMainModule:
  main()

Set Start Time and End Time

You can limit the schedules running in a designated range of time by specifying startTime and endTime.

For example,

import schedules, times, asyncdispatch, os

scheduler demoSetRange:
  every(
    seconds=1,
    id="tick",
    startTime=initDateTime(2019, 1, 1),
    endTime=now()+initDuration(seconds=10)
  ):
    echo("tick", now())

when isMainModule:
  waitFor demoSetRange.start()

Parameters startTime and endTime can be used independently. For example, you can set startTime only, or set endTime only.

ChangeLog

Released:

  • v0.2.0, 22 Jul, 2021, New feature: cron.
  • v0.1.2, 8 Jul, 2021, Bugfix: the first job schedule should be after startTime.
  • v0.1.1, update metadata.
  • v0.1.0, initial release.

License

Nim-schedules is based on MIT license.

nim-schedules's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

nim-schedules's Issues

No longer GC Safe

To quote my program:

/home/retkid/.nimble/pkgs/schedules-0.2.0/schedules/scheduler.nim(269, 6) Warning: 'start' is not GC-safe as it calls 'startNimAsyncContinue' [GcUnsafe2] /home/retkid/.nimble/pkgs/schedules-0.2.0/schedules/scheduler.nim(276, 6) Warning: 'serve' is not GC-safe as it calls 'start' [GcUnsafe2]

Error: ambiguous call; both scheduler.newSettings

hii when i try to use nim-schedules with jester it gives the below error

code

import times, asyncdispatch, schedules
import jester
scheduler mySched:
  every(seconds=1, id="sync tick"):
    echo("sync tick, seconds=1 ", now())
router myRouter:
   post "/api/deletedomain":
        resp "test"
proc main() =
  asyncCheck mySched.start()
  var jester = initJester(myrouter)
  jester.serve()

when isMainModule:
  main()

error:

ambiguous call; both scheduler.newSettings(appName: string, errorHandler: proc (fut: Future[system.void]){.closure, gcsafe.}) [proc declared in /home/user/.nimble/pkgs/schedules-0.2.0/schedules/scheduler.nim(235, 6)] and jester.newSettings(port: Port, staticDir: string, appName: string, bindAddr: string, reusePort: bool, futureErrorHandler: proc (fut: Future[system.void]){.closure, gcsafe.}) [proc declared in /home/user/.nimble/pkgs/jester-0.5.0/jester.nim(407, 6)] match for: ()

Error: type mismatch: got <id: string, interval: TimeInterval, throttleNum: int literal(1), startTime: Option[times.DateTime], endTime: Option[times.DateTime], asyncProc: proc (): Future[system.void]{.locks: <unknown>.}>

version: nim 1.3.5
package version: 0.1.0

/home/jake/Documents/catrack/src/catrack.nim(41, 11) template/generic instantiation of `scheduler` from here
        ... /home/jake/.nimble/pkgs/schedules-#head/schedules.nim(342, 16) Error: type mismatch: got <id: string, interval: TimeInterval, throttleNum: int literal(1), startTime: Option[times.DateTime], endTime: Option[times.DateTime], asyncProc: proc (): Future[system.void]{.locks: <unknown>.}>
        ... but expected one of: 
        ... proc initBeater(interval: TimeInterval; asyncProc: BeaterAsyncProc;
        ...                startTime: Option[DateTime] = none(DateTime);
        ...                endTime: Option[DateTime] = none(DateTime); id: string = "";
        ...                throttleNum: int = 1): Beater
        ...   first type mismatch at position: 6
        ...   required type for asyncProc: BeaterAsyncProc
        ...   but expression 'asyncProc = proc (): owned(Future[void]) =
        ...   template await(f`gensym53840047: typed): untyped {.used.} =
        ...     static :
        ...       error "await expects Future[T], got " & $typeof(f`gensym53840047)
        ...   template await[T](f`gensym53840048: Future[T]): auto {.used.} =
        ...     var internalTmpFuture`gensym53840049_53845020: FutureBase = f`gensym53840048
        ...     yield internalTmpFuture`gensym53840049_53845020
        ...     (cast[type(f`gensym53840048)](internalTmpFuture`gensym53840049_53845020)).read()
        ...   var retFuture_53840040 = newFuture("anonymous")
        ...   iterator anonymousIter_53840041(): owned(FutureBase) {.closure.} =
        ...     echo(["updating shows"])
        ...     let account = getAccount()
        ...     for show in items(get(select(table(RDB(), "show"), ["id"]))):
        ...       let showID = getInt(show["id"], 0)
        ...       let showDetails =
        ...         var internalTmpFuture`gensym53840049`gensym53845048: FutureBase = getShowDetails(
        ...             account, showID)
        ...         yield internalTmpFuture`gensym53840049`gensym53845048
        ...         read(cast[type(getShowDetails(account, showID))](internalTmpFuture`gensym53840049`gensym53845048))
        ...       for season in items(showDetails.seasons):
        ...         if season.season_number == 0:
        ...           continue
        ...         let seasonDetails =
        ...           var internalTmpFuture`gensym53840049`gensym53860216: FutureBase = getSeason(
        ...               account, showID, season.season_number)
        ...           yield internalTmpFuture`gensym53840049`gensym53860216
        ...           read(cast[type(getSeason(account, showID, season.season_number))](internalTmpFuture`gensym53840049`gensym53860216))
        ...         for episode in items(seasonDetails.episodes):
        ...           try:
        ...             insert(table(RDB(), "episode"), `%`([("showID", `%`(showDetails.id)),
        ...                 ("season", `%`(season.season_number)),
        ...                 ("episode", `%`(episode.episode_number)), ("id", `%`(episode.id)),
        ...                 ("airdate", `%`(episode.air_date)), ("status", `%`(0))]))
        ...           except:
        ...             echo(["already in db"])
        ...             update(where(where(where(table(RDB(), "episode"), "showID", "=", showID),
        ...                                "season", "=", season.season_number), "episode", "=",
        ...                          episode.episode_number),
        ...                    `%`([("airdate", `%`(episode.air_date))]))
        ...     complete(retFuture_53840040)
        ...   let retFutUnown`gensym53840043 = retFuture_53840040
        ...   var nameIterVar`gensym53840044 = anonymousIter_53840041
        ...   proc anonymousNimAsyncContinue_53840042() {.closure.} =
        ...     try:
        ...       if not finished(nameIterVar`gensym53840044):
        ...         var next`gensym53840045 = nameIterVar`gensym53840044()
        ...         while not isNil(next`gensym53840045) and finished(next`gensym53840045):
        ...           next`gensym53840045 = nameIterVar`gensym53840044()
        ...           if finished(nameIterVar`gensym53840044):
        ...             break
        ...         if next`gensym53840045 == nil:
        ...           if not finished(retFutUnown`gensym53840043):
        ...             let msg`gensym53840046 = "Async procedure ($1) yielded `nil`, are you await\'ing a `nil` Future?"
        ...             raise
        ...               (ref AssertionDefect)(msg: msg`gensym53840046 % "anonymous",
        ...                                    parent: nil)
        ...         else:
        ...           {.gcsafe.}:
        ...             {.push, hint[ConvFromXtoItselfNotNeeded]: false.}
        ...             addCallback(next`gensym53840045, cast[proc () {.closure, gcsafe.}](anonymousNimAsyncContinue_53840042))
        ...             {.pop.}
        ...     except:
        ...       if finished(retFutUnown`gensym53840043):
        ...         raise
        ...       else:
        ...         fail(retFutUnown`gensym53840043, getCurrentException())
        ...   
        ...   anonymousNimAsyncContinue_53840042()
        ...   return retFuture_53840040' is of type: proc (): Future[system.void]{.locks: <unknown>.}
        ...   This expression is not GC-safe. Annotate the proc with {.gcsafe.} to get extended error information.
        ... proc initBeater(interval: TimeInterval; threadProc: BeaterThreadProc;
        ...                startTime: Option[DateTime] = none(DateTime);
        ...                endTime: Option[DateTime] = none(DateTime); id: string = "";
        ...                throttleNum: int = 1): Beater
        ...   first type mismatch at position: 6
        ...   unknown named parameter: asyncProc
        ... expression: initBeater(id = "shows", interval = initTimeInterval(0, 0, 0, 0, 0, 0, 1, 0, 0, 0),
        ...            throttleNum = 1, startTime = none(DateTime), endTime = none(DateTime), asyncProc = proc (): owned(
        ...     Future[void]) =
        ...   template await(f`gensym53840047: typed): untyped {.used.} =
        ...     static :
        ...       error "await expects Future[T], got " & $typeof(f`gensym53840047)
        ...   template await[T](f`gensym53840048: Future[T]): auto {.used.} =
        ...     var internalTmpFuture`gensym53840049_53845020: FutureBase = f`gensym53840048
        ...     yield internalTmpFuture`gensym53840049_53845020
        ...     (cast[type(f`gensym53840048)](internalTmpFuture`gensym53840049_53845020)).read()
        ...   var retFuture_53840040 = newFuture("anonymous")
        ...   iterator anonymousIter_53840041(): owned(FutureBase) {.closure.} =
        ...     echo(["updating shows"])
        ...     let account = getAccount()
        ...     for show in items(get(select(table(RDB(), "show"), ["id"]))):
        ...       let showID = getInt(show["id"], 0)
        ...       let showDetails =
        ...         var internalTmpFuture`gensym53840049`gensym53845048: FutureBase = getShowDetails(
        ...             account, showID)
        ...         yield internalTmpFuture`gensym53840049`gensym53845048
        ...         read(cast[type(getShowDetails(account, showID))](internalTmpFuture`gensym53840049`gensym53845048))
        ...       for season in items(showDetails.seasons):
        ...         if season.season_number == 0:
        ...           continue
        ...         let seasonDetails =
        ...           var internalTmpFuture`gensym53840049`gensym53860216: FutureBase = getSeason(
        ...               account, showID, season.season_number)
        ...           yield internalTmpFuture`gensym53840049`gensym53860216
        ...           read(cast[type(getSeason(account, showID, season.season_number))](internalTmpFuture`gensym53840049`gensym53860216))
        ...         for episode in items(seasonDetails.episodes):
        ...           try:
        ...             insert(table(RDB(), "episode"), `%`([("showID", `%`(showDetails.id)),
        ...                 ("season", `%`(season.season_number)),
        ...                 ("episode", `%`(episode.episode_number)), ("id", `%`(episode.id)),
        ...                 ("airdate", `%`(episode.air_date)), ("status", `%`(0))]))
        ...           except:
        ...             echo(["already in db"])
        ...             update(where(where(where(table(RDB(), "episode"), "showID", "=", showID),
        ...                                "season", "=", season.season_number), "episode", "=",
        ...                          episode.episode_number),
        ...                    `%`([("airdate", `%`(episode.air_date))]))
        ...     complete(retFuture_53840040)
        ...   let retFutUnown`gensym53840043 = retFuture_53840040
        ...   var nameIterVar`gensym53840044 = anonymousIter_53840041
        ...   proc anonymousNimAsyncContinue_53840042() {.closure.} =
        ...     try:
        ...       if not finished(nameIterVar`gensym53840044):
        ...         var next`gensym53840045 = nameIterVar`gensym53840044()
        ...         while not isNil(next`gensym53840045) and finished(next`gensym53840045):
        ...           next`gensym53840045 = nameIterVar`gensym53840044()
        ...           if finished(nameIterVar`gensym53840044):
        ...             break
        ...         if next`gensym53840045 == nil:
        ...           if not finished(retFutUnown`gensym53840043):
        ...             let msg`gensym53840046 = "Async procedure ($1) yielded `nil`, are you await\'ing a `nil` Future?"
        ...             raise
        ...               (ref AssertionDefect)(msg: msg`gensym53840046 % "anonymous",
        ...                                    parent: nil)
        ...         else:
        ...           {.gcsafe.}:
        ...             {.push, hint[ConvFromXtoItselfNotNeeded]: false.}
        ...             addCallback(next`gensym53840045, cast[proc () {.closure, gcsafe.}](anonymousNimAsyncContinue_53840042))
        ...             {.pop.}
        ...     except:
        ...       if finished(retFutUnown`gensym53840043):
        ...         raise
        ...       else:
        ...         fail(retFutUnown`gensym53840043, getCurrentException())
        ...   
        ...   anonymousNimAsyncContinue_53840042()
        ...   return retFuture_53840040)

my code is only the code from the readme with a few extra lines, happens when I tried to add it to another project as well

[Feature Request] Delay After instead of Delay Fixed

every(minutes=5, id="tick")

Currently, there is only an option for a fixed delay. That means, that if a process takes about 4 minutes, but the scheduler executes its tasks "every 5 minutes", then the task already gets executed 1 minute after the last one finished.

It would be helpful, if there were a bool flag, to indicate, that the scheduling count should start once the previous task finishes.

For example: delayAfter = true

Or, if one wants to really get fancy, one might implement an enum for options like these.

Example

type
  ScheduleOption* = enum
    delayAfter,
    delayFixed

Or, one could stay consistent with the current philosophy and add another scheduling type wrapped in macro.

proc processSchedule(call: NimNode): NimNode =
call.expectKind nnkCall
let cmdName = call[0].`$`
case cmdName
of "every": processEvery(call)
of "cron": processCron(call)
else: raise newException(Exception, "unknown cmd: " & cmdName)

 proc processSchedule(call: NimNode): NimNode = 
   call.expectKind nnkCall 
   let cmdName = call[0].`$` 
   case cmdName 
   of "every": processEvery(call) 
   of "cron": processCron(call)
   of "everyAfter": processEveryAfter(call)
   else: raise newException(Exception, "unknown cmd: " & cmdName) 

Anyway, thank you for this great library! ๐Ÿ˜„

Error with dotenv

I'm trying som simple job with load env file.

import os
import logging
import times
import asyncdispatch
import schedules
import dotenv

when isMainModule:
  schedules:
    every(seconds = 5, id = "test"):
      echo("Run job")
      let env = initDotEnv()
      env.load()

Traceback:

nim c -r --threads:on src/test.nim                                                                                  5.8s ๎‚ณ ะกั€ 13 ัะฝะฒ 2021 11:49:08
Hint: used config file '/etc/nim/nim.cfg' [Conf]
Hint: used config file '/etc/nim/config.nims' [Conf]
..........................................
Call
  Ident "every"
  ExprEqExpr
    Ident "seconds"
    IntLit 5
  ExprEqExpr
    Ident "id"
    StrLit "test"
  StmtList
    Call
      Ident "echo"
      StrLit "Run job"
    LetSection
      IdentDefs
        Ident "env"
        Empty
        Call
          Ident "initDotEnv"
    Call
      DotExpr
        Ident "env"
        Ident "load"
/home/unikum/Projects/test/src/test.nim(1, 8) Warning: imported and not used: 'os' [UnusedImport]
/home/unikum/Projects/test/src/test.nim(2, 8) Warning: imported and not used: 'logging' [UnusedImport]
/home/unikum/Projects/test/src/test.nim(4, 8) Warning: imported and not used: 'asyncdispatch' [UnusedImport]
CC: stdlib_io.nim
CC: stdlib_system.nim
CC: stdlib_os.nim
CC: stdlib_logging.nim
CC: stdlib_streams.nim
CC: stdlib_lexbase.nim
CC: ../../../../../../.nimble/pkgs/dotenv-1.1.1/dotenv/private/envparser.nim
CC: ../../../../../../.nimble/pkgs/dotenv-1.1.1/dotenv.nim
CC: test.nim
Hint:  [Link]
Hint: 67072 lines; 2.084s; 94.645MiB peakmem; Debug build; proj: /home/unikum/Projects/test/src/test.nim; out: /home/unikum/Projects/test/src/test [SuccessX]
Hint: /home/unikum/Projects/test/src/test  [Exec]
Run job
Traceback (most recent call last)
/home/unikum/Projects/test/src/test.nim(9) test
/home/unikum/.nimble/pkgs/schedules-0.1.0/schedules.nim(226) serve
/usr/lib/nim/pure/asyncdispatch.nim(1930) runForever
/usr/lib/nim/pure/asyncdispatch.nim(1627) poll
/usr/lib/nim/pure/asyncdispatch.nim(1368) runOnce
/usr/lib/nim/pure/asyncdispatch.nim(208) processPendingCallbacks
/usr/lib/nim/pure/asyncmacro.nim(29) fireNimAsyncContinue
/home/unikum/.nimble/pkgs/schedules-0.1.0/schedules.nim(161) fireIter
/usr/lib/nim/pure/times.nim(1324) now
/usr/lib/nim/pure/times.nim(1318) local
/usr/lib/nim/pure/times.nim(1175) inZone
/usr/lib/nim/pure/times.nim(1147) zonedTimeFromTime
/usr/lib/nim/pure/times.nim(1248) localZonedTimeFromTime
/usr/lib/nim/pure/times.nim(1241) getLocalOffsetAndDst
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Error: execution of an external program failed: '/home/unikum/Projects/test/src/test '

I also try whith while + sleep and it work fine.

task runs once despite later startTime

Nice Library.

Running the code below (with startTime set to a later date) will cause tick a to run once and then wait until the startTime. I would expect the first task to completely delay until the startTime.

import schedules, times, asyncdispatch

schedules:
  every(seconds=10, id="tick", async=true, startTime=initDateTime(7, mJul, 2021, 14, 28, 00, 00, local())):
    echo("tick a", now())

  every(seconds=10, id="atick", async=true):
    echo("tick b", now())
    await sleepAsync(3000)

Compilation error

I can't use this lib, not even the Readme example...

I got this:

โžœ  ~ cat scheduleExample.nim 
import schedules, times, asyncdispatch

schedules:
  every(seconds=10, id="tick"):
    echo("tick", now())

  every(seconds=10, id="atick", async=true):
    echo("tick", now())
    await sleepAsync(3000)
โžœ  ~ nim c -r scheduleExample.nim --threads:on
Hint: used config file '/home/glasso/.choosenim/toolchains/nim-1.4.8/config/nim.cfg' [Conf]
Hint: used config file '/home/glasso/.choosenim/toolchains/nim-1.4.8/config/config.nims' [Conf]
......................................
/home/glasso/.nimble/pkgs/schedules-0.1.1/schedules.nim(31, 46) template/generic instantiation of `async` from here
/home/glasso/.nimble/pkgs/schedules-0.1.1/schedules.nim(32, 19) Error: undeclared identifier: 'Thread'
โžœ  ~ nim --version
Nim Compiler Version 1.4.8 [Linux: amd64]
Compiled at 2021-05-25
Copyright (c) 2006-2021 by Andreas Rumpf

git hash: 44e653a9314e1b8503f0fa4a8a34c3380b26fff3
active boot switches: -d:release

I tried to run in two computers, and I get this error in both...

[Feature Request] Cron-style scheduling

Request to support cron-style scheduling. For example:

import schedules, times, asyncdispatch

schedules:
  cron(expr="0 0 * * *", id="tick"):
    echo("tick", now())

  cron(expr="@every 1m", id="atick", async=true):
    echo("tick", now())
    await sleepAsync(3000)

Milliseconds support

import schedules, times, os

schedules:
  every(milliseconds=20, id="tick", throttle=30):
    echo("tick ", now())
    sleep(5000)

Fails currently with [DivByZeroDefect]

Error: undeclared identifier: 'none'

Hello,

This library seems great, but I can't even execute the README example.

Env: nim 1.2.0

I created a main.nim file with the code in Usage (README) and executed:
nim c --threads:on main.nim

I get this error:
C:\nim\main.nim(3, 1) template/generic instantiation of schedules from here
C:\user\path.nimble\pkgs\schedules-0.1.0\schedules.nim(252, 32) Error: undeclared identifier: 'none'

Bye

Schedule to an specific time

Hi, I'd like to know if this lib allow me to schedule a task to run everyday at 00:00 UTC, for example.

Or, as a bonus, if it can handle cron expressions (like "0 0 * * *")

Cron: day-of-week correct?

Hi,

I'm using the cron module as I'm in need of a cron parser for a Nim application (so thank you for this). However, the use of Cron.getNext is giving unexpected results, at least compared to say Python's croniter:

import options
import times
import schedules

let base = parse("2022-03-07", "yyyy-MM-dd") # a Monday
let cron = newCron(
  day_of_week="0",
)
echo cron.getNext(base).get # 2022-03-07T00:00:00+00:00 (same day?)

In the above example, I would expect the next date to be 2022-03-13 (a Sunday), but it looks like the day of week is treated relative to the base date, rather than the start of the week.

If this is as you expect, I wondered if you could also explain why day_of_week="*/2" and day_of_week="*/3" yield the same result (using the snippet above).

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.